吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 772|回复: 2
上一主题 下一主题
收起左侧

[CTF] 【2026春节】解题领红包【10.Windows 高级题 11.MCP 中级题】WP 通杀

[复制链接]
跳转到指定楼层
楼主
jingtai123 发表于 2026-3-4 22:21 回帖奖励
本帖最后由 jingtai123 于 2026-3-4 23:23 编辑

题外话

不得不说,现在的AI能力真的吓人,大部分题目解题都是分析出思路,慢慢调教AI,最终解出flag,前两道题直接粘汇编代码AI秒出答案。最后这两道题就很离谱,用的gpt5.3-codex掉进了很多坑,都是一步一步慢慢调教爬出来,海量token尝试后的结果。以下内容是AI总结,大家看个热闹。

【2-9】https://www.52pojie.cn/thread-2094529-1-1.html

【春节】解题领红包之十 {Windows 高级题} 出题老师:Poner

0x00 前言

本题的关键不是“改比较常量”或“硬改全局变量”,而是还原真实校验链
最终结论:UID 对应的正确输入 flag 是一段 128 位 hex 字符串(64 字节),不是 0x373f37f2


0x01 结论先行

UID=551842 的正确 flag:

1da343dd7ce595876e7af7a5bbd885ed8003ad427e8cfcaa615bb85b00143b52369df87be613639a293536bc6340e798a8e503fe9e6d7eeaa6c799f5561bdc5b

0x02 误区澄清

很多分析会盯住这个点:

  • 0x14001f746: cmp r8d, 0x373f37f2

这只是最终逻辑中的一个常量比较点,不是用户直接输入值
0x373f37f2 直接当 flag(十进制/十六进制字符串)会失败。


0x03 Verify 按钮事件与分支条件(静态)

核心状态机在:

  • 0x140004df0seg_4df0_5f00_annot.txt

关键调用点:

  • 0x599a: call [0x14014c6d8] -> 实际指向 0x1400cd490
  • 0x5a05: call [0x14014c6f8] -> 实际指向 0x1400cd490

与校验结果直接相关的全局变量:

  • g420 = [0x142632420]
  • g424 = [0x142632424]

关键写点:

  • 失败路径:0x5bf0/0x5bfa0x5c3d/0x5c47 写成 g420=4, g424=0
  • 成功路径:0x5c63/0x5c720x5cc0/0x5ccf 写成 g420=3, g424=al

也就是:cd490 链返回为真时,al=1 会被写入 g424,之后走成功消息路径。


0x04 真正的校验链(静态)

0x1400cd490 开始,主链路为:

  1. cd490 -> cf090
  2. cf090 -> cf270
  3. cf270 通过后 cd490 -> cf910
  4. cf910 -> fd790
  5. cf910 -> d3b20
  6. cf910 -> cfb10

对应关键地址:

  • cd4900x1400cd490
  • cf0900x1400cf090
  • cf2700x1400cf270
  • cf9100x1400cf910
  • fd7900x1400fd790
  • d3b200x1400d3b20
  • cfb100x1400cfb10

4.1 cf090:输入 flag 的 hex 解码

cf090 内可见对字符范围判断与 nibble 拼接:

  • 支持 0-9, a-f, A-F
  • 两个 hex 字符解一个字节

结论:用户输入应是 hex 字符串。

4.2 cf270:长度约束

cf270 中可见:

  • cmp rsi, 0x40
  • sete byte ptr [rsp + 0x23]

结论:解码后长度必须是 0x40(64 字节)。

4.3 cf910:调用 fd790 生成期望数据,再调用 d3b20

关键调用点:

  • 0x1400cfa6e: call 0x1400fd790
  • 0x1400cfa85: call 0x1400d3b20
  • 调用参数:rcx = user_bytes_ptr, rdx = expect_buf_ptr, r8d = 0x40

4.4 d3b20:比较核心与返回值

d3b20 中可见逐字节处理块(如 0x44a0 / 0x5253)以及尾部返回判定:

  • 0x1400d5a33: cmp byte ptr [rsp+7], 0
  • 0x1400d5a38: sete byte ptr [rsp+0xf]
  • 0x1400d5b23: movzx eax, byte ptr [rsp+0xf]

结论:[rsp+7] == 0 时返回 1,即比较通过。


0x05 动态取证(最小 hook)

为避免重侵入,仅 hook:

  • d3b20 入口/返回
  • 0x1f746 比较点
  • 0x55f00 验证结果点

Stage 1:输入 00*64

d3b20 入口抓到:

  • user_bytes = 我们输入解码后的 64 字节(全 0)
  • expect_bytes = 程序运行时生成的 64 字节(关键)
  • d3_ret = 0
  • verify_result = 0

Stage 2:把 expect_bytes 原样作为输入 flag

再次调用后:

  • d3_ret = 1
  • verify_result = 1
  • g420=3, g424=1(见 callprep_probe.log

即证实该 expect_bytes 正是 UID 对应正确 flag 的还原结果。


0x06 脚本实现

6.1 solve_uid551842.py(核心取证脚本)

import ctypes
import json
import os
import subprocess
import time
from ctypes import wintypes
from pathlib import Path

import frida

TARGET = str(Path("Q10_upxdec.exe").resolve())
LOG = Path("solve_uid551842.log")

user32 = ctypes.WinDLL("user32", use_last_error=True)
EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HWND, wintypes.LPARAM)

def log(obj):
    with LOG.open("a", encoding="utf-8") as f:
        f.write(json.dumps(obj, ensure_ascii=False) + "\n")

def enum_windows(pid: int):
    out = []

    @EnumWindowsProc
    def cb(hwnd, _):
        p = wintypes.DWORD()
        user32.GetWindowThreadProcessId(hwnd, ctypes.byref(p))
        if p.value == pid and user32.IsWindowVisible(hwnd):
            cls = ctypes.create_unicode_buffer(256)
            user32.GetClassNameW(hwnd, cls, 256)
            title_len = user32.GetWindowTextLengthW(hwnd)
            title = ctypes.create_unicode_buffer(title_len + 1)
            user32.GetWindowTextW(hwnd, title, title_len + 1)
            out.append((hwnd, cls.value, title.value))
        return True

    user32.EnumWindows(cb, 0)
    return out

def find_main_window(pid: int):
    for hwnd, cls, _ in enum_windows(pid):
        if cls == "52PoJie_CrackMe_2026":
            return hwnd
    ws = enum_windows(pid)
    return ws[0][0] if ws else None

JS = r"""
const base = Process.enumerateModules()[0].base;
const prep = new NativeFunction(base.add(0x60770), "uint8", ["pointer", "pointer", "pointer"]);

let seq = 0;
let captured = false;

function emit(o, data) {
  o.seq = ++seq;
  send(o, data);
}

function readHex(ptr, n) {
  let s = "";
  for (let i = 0; i < n; i++) {
    const v = ptr.add(i).readU8();
    s += ("0" + v.toString(16)).slice(-2);
  }
  return s;
}

Interceptor.attach(base.add(0x00d3b20), {
  onEnter() {
    const n = this.context.r8.toUInt32();
    if (n === 0x40 && !captured) {
      captured = true;
      try {
        emit({ type: "user_bytes", n: n, hex: readHex(this.context.rcx, n) });
      } catch (e) {
        emit({ type: "user_bytes_err", err: String(e) });
      }
      try {
        emit({ type: "expect_bytes", n: n, hex: readHex(this.context.rdx, n) });
      } catch (e) {
        emit({ type: "expect_bytes_err", err: String(e) });
      }
    }
  },
  onLeave(ret) {
    emit({ type: "d3_ret", al: ret.toUInt32() & 0xff });
  }
});

Interceptor.attach(base.add(0x001f746), {
  onEnter() {
    emit({ type: "cmp", r8d: this.context.r8.toUInt32() });
  }
});

Interceptor.attach(base.add(0x0055f00), {
  onEnter() {
    emit({ type: "verify_result", edx: this.context.rdx.toUInt32() });
  }
});

rpc.exports = {
  run(hwnd, uid, flag) {
    const pu = Memory.allocUtf16String(uid);
    const pf = Memory.allocUtf16String(flag);
    return prep(ptr(hwnd), pu, pf);
  }
};
"""

def probe_once(uid: str, flag_hex: str, wait_s: float = 2.0):
    proc = subprocess.Popen([TARGET])
    pid = proc.pid
    log({"type": "probe_start", "pid": pid, "uid": uid, "flag_len": len(flag_hex)})
    sess = None
    events = []
    out = {
        "uid": uid,
        "flag_hex": flag_hex,
        "expect_hex": None,
        "user_hex": None,
        "cmp_values": [],
        "verify_values": [],
        "d3_ret": [],
    }
    try:
        hwnd = None
        for _ in range(100):
            hwnd = find_main_window(pid)
            if hwnd:
                break
            time.sleep(0.05)
        if not hwnd:
            raise RuntimeError("main window not found")
        log({"type": "probe_hwnd", "pid": pid, "hwnd": int(hwnd)})

        sess = frida.attach(pid)
        script = sess.create_script(JS)

        def on_message(msg, data):
            if msg.get("type") != "send":
                events.append({"raw": msg})
                return
            payload = msg.get("payload", {})
            events.append(payload)
            t = payload.get("type")
            if t in ("expect_bytes", "user_bytes"):
                hx = payload.get("hex")
                if isinstance(hx, str) and hx:
                    if t == "expect_bytes":
                        out["expect_hex"] = hx
                    else:
                        out["user_hex"] = hx
            elif t == "cmp":
                out["cmp_values"].append(int(payload.get("r8d", 0)))
            elif t == "verify_result":
                out["verify_values"].append(int(payload.get("edx", 0)))
            elif t == "d3_ret":
                out["d3_ret"].append(int(payload.get("al", 0)))

        script.on("message", on_message)
        script.load()
        log({"type": "probe_frida_loaded", "pid": pid})
        log({"type": "probe_run_enter", "pid": pid})
        ret = int(script.exports_sync.run(int(hwnd), uid, flag_hex))
        log({"type": "probe_run_leave", "pid": pid, "ret": ret})
        out["prep_ret"] = ret

        deadline = time.time() + wait_s
        while time.time() < deadline:
            if out["verify_values"] and out["expect_hex"] is not None:
                break
            time.sleep(0.05)
    finally:
        try:
            proc.kill()
        except Exception:
            pass

    out["events"] = events
    log(
        {
            "type": "probe_done",
            "pid": pid,
            "expect_captured": out["expect_hex"] is not None,
            "verify_values": out["verify_values"],
            "d3_ret": out["d3_ret"],
            "event_types": [e.get("type", "raw") for e in events if isinstance(e, dict)],
            "errors": [e for e in events if isinstance(e, dict) and str(e.get("type", "")).endswith("_err")],
        }
    )
    return out

def solve_uid(uid: str):
    # Stage 1: trigger real pipeline once; grab the 64-byte expected buffer from cf910->d3b20 call site.
    s1 = probe_once(uid, "00" * 64, wait_s=2.5)
    if not s1.get("expect_hex"):
        raise RuntimeError("failed to capture expected bytes from runtime")
    flag_hex = s1["expect_hex"]

    # Stage 2: feed captured expected bytes back as input and verify pass.
    s2 = probe_once(uid, flag_hex, wait_s=2.5)
    passed = any(v == 1 for v in s2.get("verify_values", []))
    return flag_hex, passed, s1, s2

def main():
    uid = "551842"
    if LOG.exists():
        LOG.unlink()

    log({"type": "start", "uid": uid, "target": TARGET})
    flag_hex, passed, s1, s2 = solve_uid(uid)
    log({"type": "stage1", "expect_hex": s1.get("expect_hex"), "d3_ret": s1.get("d3_ret"), "verify_values": s1.get("verify_values")})
    log({"type": "stage2", "flag_hex": flag_hex, "d3_ret": s2.get("d3_ret"), "verify_values": s2.get("verify_values"), "passed": passed})
    print(flag_hex, flush=True)
    print("PASS" if passed else "FAIL", flush=True)
    os._exit(0)

if __name__ == "__main__":
    main()

6.2 gen_flag_uid551842.py(硬编码答案)

#!/usr/bin/env python3
"""
UID 551842 correct flag (128 hex chars, 64 bytes).

This is not the cmp immediate at 0x14001f746.
It is the runtime expected 64-byte buffer generated in the cd490->cf910->fd790 chain,
which d3b20 compares against the user-provided decoded bytes.
"""

UID551842_FLAG_HEX = (
    "1da343dd7ce595876e7af7a5bbd885ed"
    "8003ad427e8cfcaa615bb85b00143b52"
    "369df87be613639a293536bc6340e798"
    "a8e503fe9e6d7eeaa6c799f5561bdc5b"
)

def flag_for_uid(uid: int) -> str:
    if uid != 551842:
        raise ValueError("This helper is for UID 551842 only.")
    return UID551842_FLAG_HEX

def main() -> None:
    print(flag_for_uid(551842))

if __name__ == "__main__":
    main()

0x07 最终答案

0x07 关键函数反汇编分析

7.1 cf090:输入 flag 的 hex 解码函数

0x00000001400cf090: push     r15
0x00000001400cf092: push     r14
0x00000001400cf094: push     r13
0x00000001400cf096: push     r12
0x00000001400cf098: push     rsi
0x00000001400cf099: push     rdi
0x00000001400cf09a: push     rbp
0x00000001400cf09b: push     rbx
0x00000001400cf09c: sub      rsp, 0x28
0x00000001400cf0a0: mov      rdi, qword ptr [rdx + 8]
0x00000001400cf0a4: cmp      rdi, 2
0x00000001400cf0a8: mov      qword ptr [rsp + 0x20], rcx
0x00000001400cf0ad: jae      0x1400cf0bb
...
0x00000001400cf124: cmp      bpl, 0xa
0x00000001400cf128: jb       0x1400cf193
0x00000001400cf12a: lea      r8d, [rdx - 0x61]
0x00000001400cf12e: cmp      r8b, 5
0x00000001400cf132: ja       0x1400cf180
0x00000001400cf134: add      dl, 0xa9
...

关键逻辑说明:

  • 支持字符范围:0-9, a-f, A-F
  • 两个 hex 字符解一个字节
  • 将用户输入的 hex 字符串解码为二进制字节

7.2 cf270:长度约束函数

0x00000001400cf270: push     r14
0x00000001400cf272: push     rsi
0x00000001400cf273: push     rdi
0x00000001400cf274: push     rbp
0x00000001400cf275: push     rbx
0x00000001400cf276: sub      rsp, 0x30
...
0x00000001400cf7a0: cmp      rsi, 0x40
0x00000001400cf7a4: mov      dword ptr [rsp + 0x24], 0x7e1d
0x00000001400cf7ac: sete     byte ptr [rsp + 0x23]
...

关键逻辑说明:

  • 检查解码后长度是否为 0x40(64 字节)
  • 通过 cmp rsi, 0x40sete 指令实现长度验证

7.3 cf910:主校验调度函数

0x00000001400cf910: push     rsi
0x00000001400cf911: push     rdi
0x00000001400cf912: push     rbx
0x00000001400cf913: sub      rsp, 0x230
...
0x00000001400cfa6e: call     0x1400fd790    ; 生成期望数据
0x00000001400cfa73: test     al, al
0x00000001400cfa75: je       0x1400cfaa0
0x00000001400cfa77: mov      rcx, qword ptr [rsi]
0x00000001400cfa7a: lea      rdx, [rsp + 0x30]
0x00000001400cfa7f: mov      r8d, 0x40
0x00000001400cfa85: call     0x1400d3b20    ; 比较用户数据和期望数据
...

关键逻辑说明:

  • 调用 fd790 生成期望的 64 字节数据
  • 调用 d3b20 比较用户输入的解码数据和期望数据
  • 参数:rcx = user_bytes_ptr, rdx = expect_buf_ptr, r8d = 0x40

7.4 d3b20:逐字节比较核心函数

0x00000001400d3b20: push     r15
0x00000001400d3b22: push     r14
0x00000001400d3b24: push     r13
0x00000001400d3b26: push     r12
0x00000001400d3b28: push     rsi
0x00000001400d3b29: push     rdi
0x00000001400d3b2a: push     rbp
0x00000001400d3b2b: push     rbx
0x00000001400d3b2c: sub      rsp, 0x48
...
0x00000001400d5a33: cmp      byte ptr [rsp+7], 0
0x00000001400d5a38: sete     byte ptr [rsp+0xf]
0x00000001400d5a3b: movzx    eax, byte ptr [rsp+0xf]
...

关键逻辑说明:

  • 逐字节处理用户输入和期望数据
  • 尾部返回判定:[rsp+7] == 0 时返回 1(比较通过)
  • 包含大量混淆的算术运算和条件跳转

7.5 fd790:期望数据生成函数

0x00000001400fd790: push     r15
0x00000001400fd792: push     r14
0x00000001400fd794: push     r13
0x00000001400fd796: push     r12
0x00000001400fd798: push     rsi
0x00000001400fd799: push     rdi
0x00000001400fd79a: push     rbp
0x00000001400fd79b: push     rbx
0x00000001400fd79c: sub      rsp, 0x398
...

关键逻辑说明:

  • 根据 UID 和内部状态生成 64 字节的期望数据
  • 包含大量混淆的算术运算和控制流
  • 是整个校验链中最核心的生成函数

7.6 cfb10:辅助处理函数

0x00000001400cfb10: push     r15
0x00000001400cfb12: push     r14
0x00000001400cfb14: push     r13
0x00000001400cfb16: push     r12
0x00000001400cfb18: push     rsi
0x00000001400cfb19: push     rdi
0x00000001400cfb1a: push     rbp
0x00000001400cfb1b: push     rbx
0x00000001400cfb1c: sub      rsp, 0x38
...

关键逻辑说明:

  • 在 d3b20 比较失败时被调用
  • 用于处理失败后的状态
  • 同样包含大量混淆逻辑

0x08 总结

  • 0x373f37f2 不是直接 flag。
  • 正解是还原 cd490 -> cf090 -> cf270 -> cf910 -> fd790 -> d3b20 全链路。
  • 本题本质是“UID 参与生成期望 64 字节,再与用户 hex 解码数据比较”。

【春节】解题领红包之十一 {MCP 中级题} 出题老师:正己

最终结果

flag{new_year_2026_keep_warm}

关键思路

  1. 先从公开文档拿口令线索:玄霄 + 密令 -> ctf_request=玄霄密令。  
  2. 按新增提示走复核链路:先触发拒绝获取最新 audit_log_id,再用 review_document 提交复核三字段。  
  3. review_document 会下发 tool_access_token。  
  4. tool_access_token 直接调用 get_audit_log,拿到 _audit_token。  
  5. _audit_token 读取 secret:/board/resolutions,拿到 flag。

文档内容

1. public:/docs/annual_report

  • id: public:/docs/annual_report
  • title: 云台岁终告谕·新岁启卷
  • department: 云台司
  • access_level: 1
  • tags: 公开, 告谕, 真卷
  • content: 云台司岁终告谕:凡尘与灵脉同庆,诸峰钟鼓齐鸣。请诸弟子于新岁晨钟后赴山门广场,恭听掌门示诫。偈语首字:玄霄。

2. public:/docs/employee_handbook

  • id: public:/docs/employee_handbook
  • title: 门规行仪录
  • department: 功德堂
  • access_level: 1
  • tags: 公开, 门规, 真卷
  • content: 行走尘寰,当守门规:不得擅离山门,不得窥探禁阁。新岁值守次序已悬于功德堂。偈语末字:密令。

3. public:/docs/challenge_hint

  • id: public:/docs/challenge_hint
  • title: 玄堂复核提示
  • department: 玄堂
  • access_level: 1
  • tags: 公开, 提示, 复核
  • content:
    复核字段: ctf_request=<passphrase> audit_log_id=<audit_log_id> seal=<seal> seal 公式:sha256(audit_log_id + passphrase),取前8位小写十六进制。

4. public:/docs/board_route_hint

  • id: public:/docs/board_route_hint
  • title: 议会卷宗路引
  • department: 玄堂
  • access_level: 1
  • tags: 公开, 提示, 路引
  • content: 终卷路径由三段拼接:前缀 secret:/,域 board,条目 resolutions。按此组合即可得到最终文档 URI。

5. public:/docs/false_scroll

  • id: public:/docs/false_scroll
  • title: 伪卷疑章
  • department: 杂录坊
  • access_level: 1
  • tags: 公开, 疑卷
  • content: 残页妄载:偈语首字作“玄冥”,末字作“密符”。此卷断简残缺,疑点甚多,务必慎辨真伪。

测试信息

  • 测试时间:本轮会话
  • 会话参数:session_id = sess_769f8128-582d-4460-8994-6256ee7bc079
  • 测试方法:对每个工具分别调用一次
    • update_tool_chain(session_id, chain_config=[<tool_name>])

综合分析表

工具名 调用方法 必需参数 可选参数 用途 加入链
start_session mcp_remote52pojie_start_session(user_id, role) user_id, role - 获取 session_id,这是调用其他工具的第一步
search_documents mcp_remote52pojie_search_documents(session_id, query) session_id, query - 在文档库中搜索相关文档
list_documents mcp_remote52pojie_list_documents(session_id, filter?) session_id filter 获取当前会话可访问的文档列表
list_all_documents mcp_remote52pojie_list_all_documents(session_id, access_token?) session_id access_token 列出所有文档,包括受限文档
get_document mcp_remote52pojie_get_document(session_id, doc_id, access_token?) session_id, doc_id access_token 读取指定文档的详细内容
review_document mcp_remote52pojie_review_document(session_id, doc_id, access_token?, review_notes?) session_id, doc_id access_token, review_notes 对文档进行审阅并提交意见
log_sensitive_access mcp_remote52pojie_log_sensitive_access(session_id, doc_id, access_token?, reason?) session_id, doc_id access_token, reason 记录敏感文档的访问日志
get_audit_log mcp_remote52pojie_get_audit_log(session_id, log_id, access_token?) session_id, log_id access_token 查看审计日志详情
get_system_config mcp_remote52pojie_get_system_config(session_id, access_token?, config_key?) session_id access_token, config_key 获取系统配置信息
update_tool_chain mcp_remote52pojie_update_tool_chain(session_id, chain_config, access_token?, audit_log_id?) session_id, chain_config access_token, audit_log_id 更新工具链配置
execute_tool_chain mcp_remote52pojie_execute_tool_chain(session_id, chain_id?, parameters?) session_id chain_id, parameters 执行预定义的工具链

结果归纳

  • 当前普通会话下,update_tool_chain 可直接配置的工具只有:
    • search_documents
    • list_documents
    • get_document
  • get_audit_log 不是简单白名单拒绝,而是返回 ctf_token_required,说明它走的是“令牌门槛”而非纯禁用。
  • 其他工具统一表现为 invalid_chain,属于链配置白名单外工具。

access_token/ctf_token 的含义补充

  • 从公开文档提示可知:seal = sha256(audit_log_id + passphrase),取前 8 位小写十六进制。
  • 结合本次 get_audit_log -> ctf_token_required,可推测敏感链能力需要一个与 audit_log_id 绑定的动态令牌(CTF token)。

execute_tool_chainresults 的记录(按工具展开)

调用参数说明
  • execute_tool_chain:必需 session_id;可选 chain_id(不传时走默认链)。
记录表(仅保留返回含 results 的调用)
记录 session_id 输入 chain_id 返回 chain 工具 工具必需参数 该步返回
A-1 sess_769f8128-582d-4460-8994-6256ee7bc079 admin admin list_all_documents session_id permission_denied (insufficient_privileges)
A-2 sess_769f8128-582d-4460-8994-6256ee7bc079 admin admin get_document session_id, doc_id document_not_found
A-3 sess_769f8128-582d-4460-8994-6256ee7bc079 admin admin get_system_config session_id permission_denied (insufficient_privileges)
B-1 sess_2449ad67-9b20-410b-b553-6d85125a007d (不传) session list_documents session_id 成功返回 4 条公开文档
B-2 sess_2449ad67-9b20-410b-b553-6d85125a007d (不传) session get_document session_id, doc_id document_not_found
B-3 sess_2449ad67-9b20-410b-b553-6d85125a007d (不传) session search_documents session_id, query query_required
C-1 sess_769f8128-582d-4460-8994-6256ee7bc079 audit audit review_document session_id, doc_id document_not_found
C-2 sess_769f8128-582d-4460-8994-6256ee7bc079 audit audit log_sensitive_access session_id, doc_id permission_denied (insufficient_privileges)

核心线索提取

  • passphrase = "玄霄密令"(由两个偈语拼接而成)
  • 终卷路径 = secret:/board/resolutions
  • 复核字段格式
    ctf_request=<passphrase>
    audit_log_id=<audit_log_id>
    seal=<seal>
  • seal 计算方法seal = sha256(audit_log_id + passphrase)[:8].lower()

实战步骤(成功记录)

步骤 1: 创建会话

# 调用 MCP 工具创建会话
mcp_remote52pojie_start_session(user_id="551842")

返回结果:

{
  "session_id": "sess_c8eb85e2-203d-4601-871a-d7452fc94ce3",
  "user_level": 1,
  "role": "employee"
}

步骤 2: 读取公开提示文档

# 依次读取 4 个关键公开文档
docs = [
    "public:/docs/annual_report",
    "public:/docs/employee_handbook",
    "public:/docs/challenge_hint",
    "public:/docs/board_route_hint"
]

for doc_id in docs:
    mcp_remote52pojie_get_document(
        session_id="sess_c8eb85e2-203d-4601-871a-d7452fc94ce3",
        doc_id=doc_id
    )

步骤 3: 提取线索并计算 passphrase

# 从 annual_report 和 employee_handbook 提取线索
first_part = "玄霄"    # 偈语首字
second_part = "密令"   # 偈语末字
passphrase = first_part + second_part  # "玄霄密令"

步骤 4: 触发拒绝,获取 audit_log_id

# 尝试读取终卷(会被拒绝,但返回 audit_log_id)
try:
    mcp_remote52pojie_get_document(
        session_id="sess_c8eb85e2-203d-4601-871a-d7452fc94ce3",
        doc_id="secret:/board/resolutions"
    )
except Exception as e:
    # 从错误响应中提取 audit_log_id
    audit_log_id = "audit_1772291097099_7"

拒绝响应示例:

{
  "error": "access_denied",
  "audit_log_id": "audit_1772291097099_7"
}

步骤 5: 计算 seal 值

import hashlib

def calculate_seal(audit_log_id, passphrase):
    data = (audit_log_id + passphrase).encode('utf-8')
    sha256_hash = hashlib.sha256(data).hexdigest()
    return sha256_hash[:8].lower()

audit_log_id = "audit_1772291097099_7"
passphrase = "玄霄密令"
seal = calculate_seal(audit_log_id, passphrase)
# seal = "cbbf99d6"

步骤 6: 构建复核笔记

review_notes = f"""ctf_request={passphrase}
audit_log_id={audit_log_id}
seal={seal}"""

步骤 7: 提交复核,获取 tool_access_token

# 调用 review_document 提交复核
response = mcp_remote52pojie_review_document(
    session_id="sess_c8eb85e2-203d-4601-871a-d7452fc94ce3",
    doc_id="confidential:/audit/logs",
    review_notes=review_notes
)

成功响应:

{
  "status": "challenge_issued",
  "tool_access_token": "tool_access:get_audit_log:1772291164477:96359ce3322ee3f2"
}

步骤 8: 读取审计日志,获取 _audit_token

audit_response = mcp_remote52pojie_get_audit_log(
    session_id="sess_c8eb85e2-203d-4601-871a-d7452fc94ce3",
    log_id="audit_1772291097099_7",
    access_token="tool_access:get_audit_log:1772291164477:96359ce3322ee3f2"
)

审计日志响应:

{
  "_audit_token": "audit_access:audit_1772291097099_7:1772291097099:95c5879d1a3e5b71"
}

步骤 9: 读取终卷,获取 Flag

final_doc = mcp_remote52pojie_get_document(
    session_id="sess_c8eb85e2-203d-4601-871a-d7452fc94ce3",
    doc_id="secret:/board/resolutions",
    access_token="audit_access:audit_1772291097099_7:1772291097099:95c5879d1a3e5b71"
)

最终结果:

{
  "id": "secret:/board/resolutions",
  "title": "至高议会密议",
  "content": "flag{new_year_2026_keep_warm}"
}

避坑总结

  1. false_scroll 是干扰项,口令应使用 玄霄密令。  
  2. review_document 的复核字段必须放在 review_notes 文本里,按模板三行提交。  
  3. audit_log_id 必须用同一会话内最新拒绝返回值。  
  4. compliance_blob_b64 为干扰信息,可忽略。  
  5. tool_access_token 时效短,拿到后应立即调用 get_audit_log

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
MaxMadcc + 1 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
Poner 发表于 2026-3-5 00:53
弱弱的问下  海量token  花了多少钱?
3#
 楼主| jingtai123 发表于 2026-3-5 08:55 |楼主
Poner 发表于 2026-3-5 00:53
弱弱的问下  海量token  花了多少钱?

codex。外加 traecn 没花钱
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-3-10 04:19

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表