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

admin 2026-04-02 05:21:56 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文是一篇关于2026年春节解题领红包活动中Windows高级题的解题思路分享。作者详细分析了程序的校验逻辑,指出正确的flag是一段128位的hex字符串而非简单的数值。文章通过静态分析还原了真实的校验链,揭示了程序对输入进行解码、长度校验以及与内部生成数据进行比较的过程,并最终通过动态取证和脚本实现找到了正确的flag值。 综合评分: 90 文章分类: CTF,逆向分析,二进制安全,实战经验


cover_image

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

原创

吾爱pojie 吾爱pojie

吾爱破解论坛

2026年3月29日 08:26 北京

作者坛账号:jingtai123

题外话

不得不说,现在的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 按钮事件与分支条件(静态)

核心状态机在:

  • 0x140004df0

    seg_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

对应关键地址:

  • cd490

    0x1400cd490

  • cf090

    0x1400cf090

  • cf270

    0x1400cf270

  • cf910

    0x1400cf910

  • fd790

    0x1400fd790

  • d3b20

    0x1400d3b20

  • cfb10

    0x1400cfb10

4.1 cf090:输入 flag 的 hex 解码

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

  • 支持 0-9a-fA-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_ptrrdx = expect_buf_ptrr8d = 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)

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

defenum_windows(pid: int):
    out = []

    @EnumWindowsProc
    defcb(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))
        returnTrue

    user32.EnumWindows(cb, 0)
    return out

deffind_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 elseNone

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 = "";
&nbsp; for (let i = 0; i < n; i++) {
&nbsp; &nbsp; const v = ptr.add(i).readU8();
&nbsp; &nbsp; s += ("0" + v.toString(16)).slice(-2);
&nbsp; }
&nbsp; return s;
}

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

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

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

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

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

&nbsp; &nbsp; &nbsp; &nbsp; sess = frida.attach(pid)
&nbsp; &nbsp; &nbsp; &nbsp; script = sess.create_script(JS)

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

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

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

&nbsp; &nbsp; out["events"] = events
&nbsp; &nbsp; log(
&nbsp; &nbsp; &nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"type":&nbsp;"probe_done",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"pid": pid,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"expect_captured": out["expect_hex"]&nbsp;isnotNone,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"verify_values": out["verify_values"],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"d3_ret": out["d3_ret"],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"event_types": [e.get("type",&nbsp;"raw")&nbsp;for&nbsp;e&nbsp;in&nbsp;events&nbsp;ifisinstance(e,&nbsp;dict)],
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;"errors": [e&nbsp;for&nbsp;e&nbsp;in&nbsp;events&nbsp;ifisinstance(e,&nbsp;dict)&nbsp;andstr(e.get("type",&nbsp;"")).endswith("_err")],
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; )
&nbsp; &nbsp;&nbsp;return&nbsp;out

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

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

defmain():
&nbsp; &nbsp; uid =&nbsp;"551842"
&nbsp; &nbsp;&nbsp;if&nbsp;LOG.exists():
&nbsp; &nbsp; &nbsp; &nbsp; LOG.unlink()

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

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; main()

6.2 gen_flag_uid551842.py(硬编码答案)

&nbsp;复制代码&nbsp;隐藏代码
#!/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 = (
&nbsp; &nbsp;&nbsp;"1da343dd7ce595876e7af7a5bbd885ed"
&nbsp; &nbsp;&nbsp;"8003ad427e8cfcaa615bb85b00143b52"
&nbsp; &nbsp;&nbsp;"369df87be613639a293536bc6340e798"
&nbsp; &nbsp;&nbsp;"a8e503fe9e6d7eeaa6c799f5561bdc5b"
)

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

defmain() ->&nbsp;None:
&nbsp; &nbsp;&nbsp;print(flag_for_uid(551842))

if&nbsp;__name__ ==&nbsp;"__main__":
&nbsp; &nbsp; main()

0x07 最终答案

0x07 关键函数反汇编分析

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

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

关键逻辑说明:

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

7.2 cf270:长度约束函数

&nbsp;复制代码&nbsp;隐藏代码
0x00000001400cf270: push &nbsp; &nbsp; r14
0x00000001400cf272: push &nbsp; &nbsp; rsi
0x00000001400cf273: push &nbsp; &nbsp; rdi
0x00000001400cf274: push &nbsp; &nbsp; rbp
0x00000001400cf275: push &nbsp; &nbsp; rbx
0x00000001400cf276: sub &nbsp; &nbsp; &nbsp;rsp, 0x30
...
0x00000001400cf7a0: cmp &nbsp; &nbsp; &nbsp;rsi, 0x40
0x00000001400cf7a4: mov &nbsp; &nbsp; &nbsp;dword ptr [rsp + 0x24], 0x7e1d
0x00000001400cf7ac: sete &nbsp; &nbsp; byte ptr [rsp + 0x23]
...

关键逻辑说明:

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

7.3 cf910:主校验调度函数

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

关键逻辑说明:

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

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

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

关键逻辑说明:

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

7.5 fd790:期望数据生成函数

&nbsp;复制代码&nbsp;隐藏代码
0x00000001400fd790: push &nbsp; &nbsp; r15
0x00000001400fd792: push &nbsp; &nbsp; r14
0x00000001400fd794: push &nbsp; &nbsp; r13
0x00000001400fd796: push &nbsp; &nbsp; r12
0x00000001400fd798: push &nbsp; &nbsp; rsi
0x00000001400fd799: push &nbsp; &nbsp; rdi
0x00000001400fd79a: push &nbsp; &nbsp; rbp
0x00000001400fd79b: push &nbsp; &nbsp; rbx
0x00000001400fd79c: sub &nbsp; &nbsp; &nbsp;rsp, 0x398
...

关键逻辑说明:

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

7.6 cfb10:辅助处理函数

&nbsp;复制代码&nbsp;隐藏代码
0x00000001400cfb10: push &nbsp; &nbsp; r15
0x00000001400cfb12: push &nbsp; &nbsp; r14
0x00000001400cfb14: push &nbsp; &nbsp; r13
0x00000001400cfb16: push &nbsp; &nbsp; r12
0x00000001400cfb18: push &nbsp; &nbsp; rsi
0x00000001400cfb19: push &nbsp; &nbsp; rdi
0x00000001400cfb1a: push &nbsp; &nbsp; rbp
0x00000001400cfb1b: push &nbsp; &nbsp; rbx
0x00000001400cfb1c: sub &nbsp; &nbsp; &nbsp;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_idrole | – | 获取 session_id,这是调用其他工具的第一步 | ❌ | | search_documents | mcp_remote52pojie_search_documents(session_id, query) | session_idquery | – | 在文档库中搜索相关文档 | ✅ | | 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_iddoc_id | access_token | 读取指定文档的详细内容 | ✅ | | review_document | mcp_remote52pojie_review_document(session_id, doc_id, access_token?, review_notes?) | session_iddoc_id | access_tokenreview_notes | 对文档进行审阅并提交意见 | ❌ | | log_sensitive_access | mcp_remote52pojie_log_sensitive_access(session_id, doc_id, access_token?, reason?) | session_iddoc_id | access_tokenreason | 记录敏感文档的访问日志 | ❌ | | get_audit_log | mcp_remote52pojie_get_audit_log(session_id, log_id, access_token?) | session_idlog_id | access_token | 查看审计日志详情 | ❌ | | get_system_config | mcp_remote52pojie_get_system_config(session_id, access_token?, config_key?) | session_id | access_tokenconfig_key | 获取系统配置信息 | ❌ | | update_tool_chain | mcp_remote52pojie_update_tool_chain(session_id, chain_config, access_token?, audit_log_id?) | session_idchain_config | access_tokenaudit_log_id | 更新工具链配置 | ❌ | | execute_tool_chain | mcp_remote52pojie_execute_tool_chain(session_id, chain_id?, parameters?) | session_id | chain_idparameters | 执行预定义的工具链 | ❌ |

结果归纳

  • 当前普通会话下,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_chain 有 results 的记录(按工具展开)

调用参数说明
  • 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_iddoc_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_iddoc_id | document_not_found | | B-3 | sess_2449ad67-9b20-410b-b553-6d85125a007d | (不传) | session | search_documents | session_idquery | query_required | | C-1 | sess_769f8128-582d-4460-8994-6256ee7bc079 | audit | audit | review_document | session_iddoc_id | document_not_found | | C-2 | sess_769f8128-582d-4460-8994-6256ee7bc079 | audit | audit | log_sensitive_access | session_iddoc_id | permission_denied (insufficient_privileges) |

核心线索提取

  • passphrase

    = “玄霄密令”(由两个偈语拼接而成)

  • 终卷路径

    secret:/board/resolutions

  • 复核字段格式

  &nbsp;复制代码&nbsp;隐藏代码
  ctf_request=<passphrase>
  audit_log_id=<audit_log_id>
  seal=<seal>
  • seal 计算方法

    seal = sha256(audit_log_id + passphrase)[:8].lower()

实战步骤(成功记录)

步骤 1: 创建会话

&nbsp;复制代码&nbsp;隐藏代码
# 调用 MCP 工具创建会话
mcp_remote52pojie_start_session(user_id="551842")

返回结果:

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"session_id":"sess_c8eb85e2-203d-4601-871a-d7452fc94ce3",
&nbsp;&nbsp;"user_level":1,
&nbsp;&nbsp;"role":"employee"
}

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

&nbsp;复制代码&nbsp;隐藏代码
# 依次读取 4 个关键公开文档
docs = [
&nbsp; &nbsp;&nbsp;"public:/docs/annual_report",
&nbsp; &nbsp;&nbsp;"public:/docs/employee_handbook",
&nbsp; &nbsp;&nbsp;"public:/docs/challenge_hint",
&nbsp; &nbsp;&nbsp;"public:/docs/board_route_hint"
]

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

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

&nbsp;复制代码&nbsp;隐藏代码
# 从 annual_report 和 employee_handbook 提取线索
first_part =&nbsp;"玄霄"&nbsp; &nbsp;&nbsp;# 偈语首字
second_part =&nbsp;"密令"&nbsp; &nbsp;# 偈语末字
passphrase = first_part + second_part &nbsp;# "玄霄密令"

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

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

拒绝响应示例:

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"error":"access_denied",
&nbsp;&nbsp;"audit_log_id":"audit_1772291097099_7"
}

步骤 5: 计算 seal 值

&nbsp;复制代码&nbsp;隐藏代码
import&nbsp;hashlib

defcalculate_seal(audit_log_id, passphrase):
&nbsp; &nbsp; data = (audit_log_id + passphrase).encode('utf-8')
&nbsp; &nbsp; sha256_hash = hashlib.sha256(data).hexdigest()
&nbsp; &nbsp;&nbsp;return&nbsp;sha256_hash[:8].lower()

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

步骤 6: 构建复核笔记

&nbsp;复制代码&nbsp;隐藏代码
review_notes =&nbsp;f"""ctf_request={passphrase}
audit_log_id={audit_log_id}
seal={seal}"""

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

&nbsp;复制代码&nbsp;隐藏代码
# 调用 review_document 提交复核
response = mcp_remote52pojie_review_document(
&nbsp; &nbsp; session_id="sess_c8eb85e2-203d-4601-871a-d7452fc94ce3",
&nbsp; &nbsp; doc_id="confidential:/audit/logs",
&nbsp; &nbsp; review_notes=review_notes
)

成功响应:

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"status":"challenge_issued",
&nbsp;&nbsp;"tool_access_token":"tool_access:get_audit_log:1772291164477:96359ce3322ee3f2"
}

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

&nbsp;复制代码&nbsp;隐藏代码
audit_response = mcp_remote52pojie_get_audit_log(
&nbsp; &nbsp; session_id="sess_c8eb85e2-203d-4601-871a-d7452fc94ce3",
&nbsp; &nbsp; log_id="audit_1772291097099_7",
&nbsp; &nbsp; access_token="tool_access:get_audit_log:1772291164477:96359ce3322ee3f2"
)

审计日志响应:

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"_audit_token":"audit_access:audit_1772291097099_7:1772291097099:95c5879d1a3e5b71"
}

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

&nbsp;复制代码&nbsp;隐藏代码
final_doc = mcp_remote52pojie_get_document(
&nbsp; &nbsp; session_id="sess_c8eb85e2-203d-4601-871a-d7452fc94ce3",
&nbsp; &nbsp; doc_id="secret:/board/resolutions",
&nbsp; &nbsp; access_token="audit_access:audit_1772291097099_7:1772291097099:95c5879d1a3e5b71"
)

最终结果:

&nbsp;复制代码&nbsp;隐藏代码
{
&nbsp;&nbsp;"id":"secret:/board/resolutions",
&nbsp;&nbsp;"title":"至高议会密议",
&nbsp;&nbsp;"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

-官方论坛

www.52pojie.cn

👆👆👆

公众号设置“星标”,不会错过新的消息通知

开放注册、精华文章和周边活动等公告


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:吾爱破解论坛 吾爱pojie 吾爱pojie《【2026春节】解题领红包【10.Windows 高级题 11.MCP 中级题】WP 通杀》

评论:0   参与:  0