2026 吾爱破解春节解题(day1-day6+mcp中级题)
Day1 - 送分题
关注论坛微信公众号:吾爱破解论坛,回复“52TDL”获得口令。
Day2 - Windows 初级题
一、题目概览
本题的核心在于:
- 先通过长度与内容完全匹配的方式校验输入
- 再做一次自定义 checksum 校验
- 所有校验全部通过后,原样输出 "Correct flag: <你的输入>"
二、入口与主逻辑定位
在 Strings 窗口中搜索关键词:
"Correct flag" → 字符串地址 0x4D3201
对该字符串做交叉引用(xrefs),可以定位到函数 sub_4CD130
反编译 sub_4CD130 可以看到核心逻辑:
int __cdecl sub_4CD130(char a1)
{
...
sub_4C7E50(..., "Hint: Fake flag; length is key");
...
sub_4C7E50(..., "\n[?] Enter the password: ");
v2 = sub_401560(); // 读取用户输入
sub_4C5840(&dword_4D25E0, &v22, v2);
Str = v22; // Str 指向输入字符串
v3 = sub_401740((int)v22); // 一次简单过滤 / 假 flag 检测
v4 = 53; // '5'
v5 = v3;
v6 = 0;
if ( !v5 )
{
while ( Str[v6] == v4 )
{
if ( ++v6 == 16 )
{
sub_4C7E50(..., "\n[!] You're getting closer...");
goto LABEL_9;
}
v4 = byte_4D3032[v6]; // 与某个 16 字节序列逐字比对
}
if ( strlen(Str) != 31 )
{
sub_4C7E50(..., "\n[!] Hint: The length is your first real challenge.");
goto LABEL_9;
}
if ( (unsigned __int8)sub_4016D0(Str, 31) ) // 关键内容校验
{
// 通过 sub_4016D0 后,继续做 checksum 校验
Str = 0;
v8 = *v22;
if ( !*v22 )
goto LABEL_16;
v9 = 0;
do
{
Str += ++v9 * v8;
v8 = v22[v9];
}
while ( v8 );
if ( Str != (char *)44709 )
{
sub_4C7E50(..., "\n[!] Checksum failed! Something is wrong...");
}
else
{
// 所有检查通过,输出成功信息与 flag
sub_4C7E50(..., "\n========================================");
sub_4C7E50(..., "[+] Correct flag: ");
sub_4C4330(..., v22, v23); // 打印我们的输入
}
}
}
}
从这段逻辑可以得到几个关键信息:
sub_401740 用来过滤某些"假 flag",如果返回非 0,就直接输出 "Nice try"
- 接着有一个循环,将输入前 16 字节与
byte_4D3032 里的一串字节逐个比较
- 真正的 flag 需要:
- 长度为 31 字节(
strlen(Str) == 31)
- 通过
sub_4016D0(Str, 31) 的逐字节校验
- 再通过 checksum 条件:$\sum_{i=0}^{n-1} (i+1) \times \text{ord}(s[i]) = 44709$
三、内容校验函数 sub_4016D0
反编译 sub_4016D0:
bool __cdecl sub_4016D0(int a1, int a2)
{
unsigned __int8 *Block;
Block = (unsigned __int8 *)sub_4CB710(0x64u);
sub_401620(Block); // 关键:在 Block 中生成目标序列
if ( a2 <= 0 )
{
v4 = 0;
}
else
{
v3 = 0;
v4 = 0;
do
{
v5 = *(char *)(a1 + v3) == Block[v3];
++v3;
v4 += v5;
}
while ( a2 != v3 );
}
j_j_free(Block);
return a2 == v4; // 只有全部字符都相等才返回 true
}
可以看出:
sub_4016D0 内部先调用 sub_401620 在一块缓冲区中生成真正的"答案串"
- 然后将我们的输入与 Buffer 的前
a2 个字节逐一比较
- 每相同一个字符就
v4++,最后要求 v4 == a2
因此,要找到正确口令,就必须还原 sub_401620 生成的那 31 个字节。
四、还原 sub_401620 生成的密文
反编译 sub_401620:
_OWORD *__cdecl sub_401620(int a1)
{
*(_DWORD *)a1 = 758280311;
*(_DWORD *)(a1 + 4) = 1663511336;
*(_DWORD *)(a1 + 8) = 1880974179;
*(_DWORD *)(a1 + 12) = 494170226;
*(_DWORD *)(a1 + 16) = 842146570;
*(_DWORD *)(a1 + 20) = 657202491;
*(_DWORD *)(a1 + 24) = 658185525;
*(_BYTE *)(a1 + 30) = 99;
*(_WORD *)(a1 + 28) = 12323;
do
*result++ ^= 0x42u;
while ( result != (_BYTE *)(a1 + 31) );
*(_BYTE *)(a1 + 31) = 0;
return result;
}
关键点:
- 先往
a1 这块缓冲区写入一系列整数(小端存储)
- 然后从
a1 开始到 a1 + 31,逐字节执行 ^= 0x42(与 0x42 异或)
- 最后在偏移 31 写入
0 作为字符串结束符
用 Python 脚本还原:
nums = [758280311, 1663511336, 1880974179,
494170226, 842146570, 657202491, 658185525]
buf = bytearray()
for n in nums:
buf += n.to_bytes(4, "little", signed=True)
buf += (12323).to_bytes(2, "little", signed=True)
buf.append(99)
buf = buf[:31]
flag = "".join(chr(b ^ 0x42) for b in buf)
print(flag)
五、最终答案
52pojie!!!_2026_Happy_new_year!
验证:长度为 31 字节,checksum 计算结果为 44709,符合所有校验条件。
Day3 - Android 初级题
题目描述
玩玩游戏就过关了,新年快乐,别感冒!
Day4 - Windows 初级题
一、题目信息
- 题目名称: 【2026 春节】解题领红包之四 {Windows 初级题}
- 出题老师: 云在天
- 题目类型: Windows 逆向 / Python 反编译
- 难度: 初级
二、工具准备
- pyinstxtractor: PyInstaller 打包文件提取工具
- Python 3.x: 运行环境
- 基础工具: hexdump/xxd, base64, hashlib 等
三、解题过程
3.1 分析题目
首先运行 exe 文件,查看程序行为:
从输出可以看出:
- 这是一个 Python 打包的 exe 程序
- 需要输入正确的密码(flag)
- 提示"Decompile me if you can!"说明需要反编译
3.2 提取 PyInstaller 打包内容
使用 pyinstxtractor 工具提取 exe 文件:
python pyinstxtractor.py "【2026 春节】解题领红包之四 {Windows 初级题} 出题老师:云在天.exe"
输出结果:
提取后得到 crackme_easy.pyc 文件,这是主程序的 Python字节码文件。
3.3 分析 pyc 文件
由于 pyc 文件是用 Python 3.14 编译的,而当前系统只有 Python 3.13,直接反编译会失败。因此我们采用手动分析的方式。
通过分析 pyc 文件的二进制内容,找到以下关键字符串:
函数名:
xor_decrypt - XOR 解密函数
get_encrypted_flag - 获取加密 flag
generate_flag - 生成 flag
calculate_checksum - 计算校验和
hash_string - 哈希字符串
verify_flag - 验证 flag
fake_check_1 - 假检查函数 1
fake_check_2 - 假检查函数 2
main - 主函数
关键数据:
- 加密数据(Base64):
e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O
3.4 分析加密数据
将 Base64 编码的加密数据解码:
import base64
encrypted_b64 = "e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O"
encrypted = base64.b64decode(encrypted_b64)
# 解码后的十六进制:7b7c3e7e246f7d6d7c7e7c7864067a3e3e3763007d3963177d7a3c0e0e0e
3.5 破解 XOR 密钥
由于不知道 XOR 密钥,采用暴力破解的方式:
import base64
encrypted_b64 = "e3w+fiRvfW18fnx4ZAZ6Pj43YwB9OWMXfXo8Dg4O"
encrypted = base64.b64decode(encrypted_b64)
def xor_decrypt(data, key):
result = bytearray()
for i, byte in enumerate(data):
result.append(byte ^ key[i % len(key)])
return bytes(result)
# 尝试单字节密钥 (0x00 - 0xff)
for i in range(256):
key = bytes([i])
result = xor_decrypt(encrypted, key)
try:
decoded = result.decode('utf-8', errors='ignore')
if decoded.isprintable() and len(decoded.strip()) > 5:
print(f"密钥:0x{i:02x}")
print(f"结果:{decoded}")
except:
pass
当尝试到密钥 0x4e (ASCII 字符'N') 时,得到有意义的结果:
密钥:0x4e
结果:52p0j!3#2026*H4ppy-N3w-Y34r@@@
这个结果看起来像是一个 leet speak编码的信息:
52p0j!3# → 52pojie(吾爱破解的拼音)
2026 → 年份
H4ppy-N3w-Y34r → Happy New Year
四、最终答案
52p0j!3#2026*H4ppy-N3w-Y34r@@@
Day5 - Windows 中级题
一、题目信息
-
标题: CrackMe Challenge - Binary Edition
-
关键词: 52pojie, 2026, Happy New Year
-
提示: 1337 5p34k & 5ymb0l5! Try to decompile this in IDA!
二、初始分析
程序运行时输出提示信息,要求输入密码。通过观察文件结构或运行时的字符串,可以发现该程序是使用 Nuitka 打包的 Python 程序。核心逻辑通常位于主 DLL 文件中,在本例中为 crackme_hard.dll。
导出后查看文件夹中的内容
三、逆向分析过程
3.1 静态分析与资源定位
使用 IDA Pro 打开 crackme_hard.dll。由于 Nuitka 会将 Python字节码或源码编译为 C 代码,并进行混淆,直接查看反汇编代码较为困难。
通过搜索字符串或导入表,发现程序使用了 Windows 资源 API(FindResourceA, LoadResource 等)。这通常意味着 Python 的 payload 数据被加密存储在 PE 资源节中。
通过资源查看工具,我们在 DLL 中定位到了资源类型为 10,ID 为 3 的资源。将其提取出来,命名为 payload.bin。
3.2 Payload 结构分析
payload.bin 包含了 Nuitka 运行所需的模块数据。通过分析文件结构,我们识别出了 __main__ 模块的数据记录。将其单独提取为 main_record.bin。
所用脚本如下:
import pefile
import struct
dll_path = r"D:\Desktop\吾爱破解\day5\cEVm8pds\crackme.exe_extracted\crackme_hard.dll"
pe = pefile.PE(dll_path)
# Extract Resource
print("Extracting resource...")
found = False
if hasattr(pe, 'DIRECTORY_ENTRY_RESOURCE'):
for entry in pe.DIRECTORY_ENTRY_RESOURCE.entries:
if entry.id == 10: # RT_RCDATA
for sub_entry in entry.directory.entries:
if sub_entry.id == 3:
data_rva = sub_entry.directory.entries[0].data.struct.OffsetToData
size = sub_entry.directory.entries[0].data.struct.Size
data = pe.get_data(data_rva, size)
with open("payload.bin", "wb") as f:
f.write(data)
print(f"Extracted payload.bin (Size: {size})")
found = True
break
if not found:
print("Resource 10/3 not found!")
# Extract Tables
# ImageBase 0x3133e0000
# .rdata VA 0x58000, RawPtr 0x55e00
# Table1 (3E100): RVA 0x5E100 -> Offset 0x5BF00. Size: 256 * 4 = 1024 bytes.
# Table2 (3D100): RVA 0x5D100 -> Offset 0x5AF00. Size: 1024 * 4 = 4096 bytes.
try:
with open(dll_path, "rb") as f:
# Table 1
f.seek(0x5BF00)
table1 = f.read(1024)
with open("table1.bin", "wb") as t1:
t1.write(table1)
print("Extracted table1.bin")
# Table 2
f.seek(0x5AF00)
table2 = f.read(4096)
with open("table2.bin", "wb") as t2:
t2.write(table2)
print("Extracted table2.bin")
except Exception as e:
print(f"Error extracting tables: {e}")
在 main_record.bin 中,我们通过字符串搜索发现了与题目相关的关键变量名:
a_parts: 看起来像是一个包含口令分片的列表
lQa_key: 变量名中的 Qa 暗示了某种关联,且 Q 可能是密钥
a_total_len: 值为 30,提示了最终口令的长度
3.3 数据结构与解密
查看 a_parts 附近的数据,发现它是一个混合列表,包含字符串和整数。数据的大致结构如下(十六进制):
4c 0a <- 列表长度 10
63 64 63 21 ... <- 字符串 "cdc!a;`b" (前缀 0x63 'c' 为类型标记)
11 <- 整数 0x11 (17)
63 63 61 63 67 ... <- 字符串 "ccacg" (前缀 0x63 'c' 为类型标记)
2f <- 整数 0x2f (47)
...
通过对已知明文(如 "Happy", "New", "Year")的猜测和异或爆破,我们确认加密算法为 XOR,密钥为 0x51(即字符 'Q')。这与变量名 lQa_key 相吻合。
解密规则:
- 字符串:首字节
0x63 ('c') 是 Nuitka 的字符串类型标记,需跳过。后续字节与 0x51 进行异或。
- 整数:字节值直接与
0x51 进行异或。
3.4 口令还原
利用解密脚本,我们还原了口令的各个部分:
| 分片 |
类型 |
密文 (Hex) |
去掉 Tag |
XOR 0x51 结果 |
| 1 |
String |
63 64 63 21 61 3b 60 62 |
64 63 21 61 3b 60 62 |
52p0j13 |
| 2 |
Int |
11 |
- |
@ |
| 3 |
String |
63 63 61 63 67 |
63 61 63 67 |
2026 |
| 4 |
Int |
2f |
- |
~ |
| 5 |
String |
63 19 65 21 21 28 |
19 65 21 21 28 |
H4ppy |
| 6 |
Int |
0e |
- |
_ |
| 7 |
String |
63 1f 62 26 |
1f 62 26 |
N3w |
| 8 |
Int |
0e |
- |
_ |
| 9 |
String |
63 08 62 65 23 |
08 62 65 23 |
Y34r |
| 10 |
String |
63 70 70 70 |
70 70 70 |
!!! |
将所有部分拼接,得到完整口令。
四、最终答案
52p0j13@2026~H4ppy_N3w_Y34r!!!
验证:口令长度为 30 字符,符合 a_total_len 的限制。输入程序后显示 "SUCCESS!"。
Day6 - 番外篇 初级题
一、题目分析
拿到 CatchTheCat.exe 后,首先观察程序图标和运行界面,或者通过 IDA Pro 查看字符串,可以判断这是一个使用 Love2D 游戏引擎开发的游戏。
Love2D 的发布通常是将 love.exe 和游戏资源包(.love 文件,本质是 Zip 格式)合并成一个可执行文件。因此,我们可以尝试从 exe 文件中提取出 Lua 脚本和资源。
所用脚本如下:
import zipfile
import os
filename = "CatchTheCat.exe"
extract_dir = "extracted"
if not os.path.exists(extract_dir):
os.makedirs(extract_dir)
try:
with zipfile.ZipFile(filename, 'r') as z:
print(f"Extracting {filename} to {extract_dir}...", flush=True)
z.extractall(extract_dir)
print("Extraction complete!", flush=True)
print("Files found:", flush=True)
for root, dirs, files in os.walk(extract_dir):
for file in files:
print(os.path.join(root, file), flush=True)
except Exception as e:
print(f"Error extracting zip: {e}", flush=True)
二、提取资源
编写脚本或使用工具检查文件末尾,发现了 Zip 文件的结束签名(End of Central Directory signature PK\x05\x06)。
直接将 CatchTheCat.exe 当作 Zip 文件解压,或者提取其尾部的 Zip 数据,得到了以下核心文件:
main.lua: 游戏主逻辑
conf.lua: 配置文件
assets/flag.dat: 加密的 Flag 文件
三、逻辑分析
打开 main.lua,搜索 "flag" 或 "win" 相关的逻辑,找到了 getWinMessage 函数:
分析代码可知:
- 需要读取
assets/flag.dat 文件
- 游戏难度必须是
hard
- 解密算法是简单的异或(XOR)运算
- 解密密钥(Key)是字符串
"52pojie"
四、解密 Flag
根据上述逻辑,编写 Python 脚本进行解密:
import os
key = b"52pojie"
file_path = "extracted/assets/flag.dat"
with open(file_path, "rb") as f:
content = f.read()
result = bytearray()
key_len = len(key)
for i in range(len(content)):
b = content[i]
k = key[i % key_len]
result.append(b ^ k)
print("Decrypted Flag:", result.decode('utf-8'))
五、最终 Flag
运行解密脚本,得到 Flag:
flag{52pojie_2026_Happy_New_Year!_>w<}
Day11 - MCP 中级题
一、题目概述
这是一道基于 MCP(Model Context Protocol)的安全挑战题目,需要通过调用 remote52pojie MCP 服务完成多阶段认证流程,最终获取隐藏在密卷中的口令。
二、解题流程
2.1 启动会话
首先启动一个 MCP 会话,建立与系统的连接:
{
"user_id": "ctf_player",
"role": "auditor"
}
返回结果:
{
"session_id": "sess_c60d3d71-3afa-4895-b3e4-3314807227b7",
"user_level": 1,
"role": "employee"
}
2.2 收集公开线索
通过列出并读取公开文档,获取关键信息:
| 文档 |
关键信息 |
| 云台岁终告谕 |
偈语首字:玄霄 |
| 门规行仪录 |
偈语末字:密令 |
| 议会卷宗路引 |
终卷路径:secret:/board/resolutions |
| 玄堂复核提示 |
复核流程说明 |
合成口令: 玄霄密令
2.3 触发拒绝获取审计编号
尝试访问受限文档 confidential:/audit/logs,触发访问拒绝:
{
"error": "access_denied",
"audit_log_id": "audit_1772434108392_421"
}
2.4 计算 seal 校验值
根据复核提示,seal 的计算公式为:
seal = sha256(audit_log_id + passphrase) 的前 8 位小写十六进制
计算过程:
echo -n "audit_1772434108392_421 玄霄密令" | sha256sum | cut -c1-8
# 输出:a8a4fb08
2.5 提交复核
使用 review_document 接口提交复核信息:
{
"ctf_request": "玄霄密令",
"audit_log_id": "audit_1772434108392_421",
"seal": "a8a4fb08"
}
复核结果:
{
"status": "challenge_issued",
"tool_access_token": "tool_access:get_audit_log:1772434129837:da1a373439848285",
"compliance_note": "chain_config 仅提交 get_audit_log"
}
2.6 配置并执行工具链
按照戒律要求,工具链只能包含 get_audit_log:
{
"tool_chain": ["get_audit_log"]
}
使用获得的 access_token 获取审计日志:
{
"_audit_token": "audit_access:audit_1772434108392_421:1772434108392:d17843d57a864576"
}
2.7 访问终卷获取 flag
使用 audit_token 访问终卷 secret:/board/resolutions:
{
"id": "secret:/board/resolutions",
"title": "至高议会密议",
"content": "flag{new_year_2026_keep_warm}"
}
三、最终答案
flag{new_year_2026_keep_warm}
四、关键知识点
4.1 复核字段说明
| 字段 |
说明 |
| ctf_request |
口令本身(非 access_token) |
| audit_log_id |
触发拒绝后返回的完整审计编号 |
| seal |
SHA256 校验值(audit_log_id + passphrase)前 8 位 |
4.2 重要注意事项
- audit_log_id 必须使用完整的拒绝编号,不可截断
- 只认可同一会话中最近一次拒绝的编号
- 复核凭据有时效性和一次性特性
- 工具链配置需保持单一职责原则
4.3 流程图
启动会话 → 收集线索 → 触发拒绝 → 计算 seal → 提交复核
↓
获取审计日志 ← 配置工具链 ← 复核通过 ←─────────┘
↓
访问终卷 → 获取 FLAG