NCTF-2026-Reverse详解
字数 4168
更新时间 2026-04-12 12:16:55
NCTF-2026 Reverse 题目详解教学文档
概述
本文档对 NCTF-2026 逆向工程类题目进行详细解析,包含多道具有代表性的题目分析,涵盖Android逆向、Godot游戏保护、Electron应用、虚拟机分析和二进制协议漏洞等多个技术方向。
题目一:Hook My Secret
1. 题目信息
- 类型:Android Reverse / Crypto
- 样本:app-release.apk
- 包名:com.nctf.hookmysecret
- 活动链:PatternActivity → Stage2Activity → Stage3Activity → SuccessActivity
2. 三段校验链分析
Stage1:手势密码校验
- 位置:
com.nctf.hookmysecret.ui.PatternActivity - 校验逻辑:
- 手势点序列拼接为逗号分隔字符串(如"0,1,2,4,8")
- 计算SHA-256哈希值
- 与常量比较:
4a6bc34076c8eef0f9eac59ad30d99bb4f56ecea4b0bfab92540fb655ac680f3
- 破解方法:枚举3×3网格(0-8)的所有排列组合
- 结果:Pattern =
0,1,2,4,8
Stage2:JNI Native算法
- 位置:
libhookmysecret.so中的Java_com_nctf_hookmysecret_nativebridge_NativeBridge_encryptStage2 - 算法逻辑:
初始状态: d = 0x51 对第i个输入字节x: t = rol8(((13*i + 0x42) ^ d ^ x), 3) out = (7*d + i + t) & 0xff d = (x + d + (out ^ i)) & 0xff - 目标数组:
[250, 113, 87, 185, 6, 125, 167, 156, 4, 0, 229, 239, 119, 155, 187, 95] - 逆算法:
t = (out - (7*d + i)) & 0xff y = ror8(t, 3) x = y ^ d ^ (13*i + 0x42) - 结果:Stage2 Key =
k7Xm2Pq9Wv4N8bRt
Stage3:AES/CBC解密
- 数据来源:
- Key:Stage2的结果
- IV:SQLite数据库中的
stage3_iv(Base64解码为VerifyVector1234) - 密文:
jSaMnziall55Tdr+IZc7EKUNm/N4uwrZw1QFPw6DuirfYFJZg88j6GKLhWfNljAB
- 解密结果:Flag =
NCTF{a680107e-a49b-43e1-915b-cedd25e7835a}
3. 复现脚本
#!/usr/bin/env python3
import base64, hashlib, itertools
from Crypto.Cipher import AES
def rol8(x: int, n: int) -> int:
x &= 0xFF
return ((x << n) | (x >> (8 - n))) & 0xFF
def ror8(x: int, n: int) -> int:
x &= 0xFF
return ((x >> n) | (x << (8 - n))) & 0xFF
def stage2_encrypt_like_native(inp: bytes) -> list[int]:
d = 0x51
out = []
for i, x in enumerate(inp):
t = rol8(((13 * i + 0x42) & 0xFF) ^ d ^ x, 3)
o = (7 * d + i + t) & 0xFF
out.append(o)
d = (x + d + (o ^ i)) & 0xFF
return out
def invert_stage2_target(target: list[int]) -> bytes:
d = 0x51
recovered = []
for i, o in enumerate(target):
t = (o - (7 * d + i)) & 0xFF
y = ror8(t, 3)
x = y ^ d ^ ((13 * i + 0x42) & 0xFF)
recovered.append(x)
d = (x + d + (o ^ i)) & 0xFF
return bytes(recovered)
题目二:NoMyBank!
1. 题目结构
- 主程序:NoMyBank.exe(Godot 4单文件导出)
- 扩展库:libextension.dll(加密的伪DLL)
- 资源加密:PCK资源包嵌入EXE末尾
2. 多层保护分析
第一层:Godot资源加密
- 加密位置:EXE文件偏移
0x5194000处的GDPC头 - 加密格式:
- 16字节:明文MD5
- 8字节:明文长度(小端)
- 16字节:IV
- 后续:AES-256-CFB密文
- 密钥提取:从模板全局地址获取32字节密钥
d34bff62613fdd2861f6d5942c5e99a53ef3e90adbe9091b4686859d5b7dab22
第二层:伪DLL保护
- 加密方式:RC4加密
- 密钥:
G00dLuck2U - 解密脚本:
def rc4(data: bytes, key: bytes) -> bytes:
s = list(range(256))
j = 0
for i in range(256):
j = (j + s[i] + key[i % len(key)]) & 0xff
s[i], s[j] = s[j], s[i]
out = bytearray(len(data))
i = j = 0
for idx, b in enumerate(data):
i = (i + 1) & 0xff
j = (j + s[i]) & 0xff
s[i], s[j] = s[j], s[i]
out[idx] = b ^ s[(s[i] + s[j]) & 0xff]
return bytes(out)
第三层:运行时热补丁
- 保护机制:DLL初始化时patch掉TEA校验函数,跳转到shellcode
- shellcode解密:.data段中的0x491字节数据异或0xBA
- 真实算法:
- 魔改Base64编码
- 基于0x114514的字节扰动
3. 真实校验算法
魔改Base64
- 字符表:
ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9876543210+/ - 编码顺序:标准Base64输出[s0,s1,s2,s3]改为[s1,s0,s3,s2]
二次扰动
seed = 0x114514
for i in range(len):
x = out[i]
x ^= (seed >> ((i * 8) % 24)) & 0xff
x = rol8(x, 2) ^ 0xBA
out[i] = x
seed = seed * 0x1010193 + 0x12345678
4. 目标常量
56字节比较目标:
2b f7 67 5e 7c 98 ed 6d d1 8c ef 57 bb 33 22 7e
b2 1f 34 5b 36 6c 2b af bb 5b 12 d6 3c 0a 45 27
84 6c 47 ab 2f 75 78 3e 88 89 2d 7a cd 5c f6 fa
36 73 ff 6e d3 4c 1c 75
5. 最终结果
- Key:
NCTF{You_deserve_this_gift_b1bd7c719cfc} - Gift:
NCTF{Y0u_d3s3rv3_th1s_g1ft_b1bd7c719cfc}
题目三:PAY-FOR-2048
1. 题目结构
- 技术栈:Electron + WebAssembly
- 核心文件:
resources/app.asarwasm-build/wasm32-unknown-unknown/release/wasm_core.wasm
2. 验证流程
verify_license阶段
- 检查
maxTile >= 256 - 验证key格式:
NCTF-XXXX-XXXX-XXXX - 规范化key:去除前缀和短横线
- 32位滚动哈希校验
- 返回sessionToken
unlock_flag阶段
- 检查
maxTile >= 2048 - 重新验证sessionToken
- 再次规范化key
- 异或流解密:
normalized_key + "|" + "arcade::unlock-seed" - 返回flag字符串
3. 关键点:哈希碰撞
- 问题:verify阶段仅使用32位哈希,存在碰撞
- 解决方案:联合约束求解
- verify哈希常量约束
- unlock输出格式约束(NCTF{...}格式)
4. Z3求解
from z3 import *
norm = [BitVec(f'n{i}', 8) for i in range(12)]
s = Solver()
# 字符集约束
for c in norm:
s.add(Or(And(c >= 48, c <= 57), And(c >= 65, c <= 90)))
# verify哈希约束
K = BitVecVal(((-1515890086) & 0xffffffff), 32)
p = BitVecVal(4951, 32)
for i in range(12):
c = ZeroExt(24, norm[i])
t = BitVecVal(40503 if (i & 1) else 17881, 32)
v = (t + c) * BitVecVal(i + 11, 32)
v = v ^ RotateLeft(p, 3)
v = v + ((c << (i % 5)) ^ K)
p = Extract(31, 0, v)
s.add(p == BitVecVal(((-57161169) & 0xffffffff), 32))
5. 最终结果
- 规范化Key:
RU57W45M2048 - 用户输入Key:
NCTF-RU57-W45M-2048 - Flag:
NCTF{bff16266-c4f2-4dbb-b270-f5ded900b54c}
题目四:Vm-Encryptor
1. VM架构分析
- 程序结构:vm-encryptor.exe + code.bin
- VM上下文结构:
struct VM {
uint32_t ip; // 指令指针
uint32_t sp; // 栈指针
uint8_t mem[0x10000]; // 64KB内存
uint32_t stack[0x1000]; // 4096项整型栈
uint32_t running; // 运行状态
uint32_t err; // 错误码
};
2. VM指令集
| opcode | 含义 | opcode | 含义 |
|---|---|---|---|
| 0x00 | jmp imm32 | 0x10 | or |
| 0x01 | jz imm32 | 0x11 | not |
| 0x02 | jnz imm32 | 0x12 | xor |
| 0x03 | push8 imm8 | 0x13 | shl |
| 0x04 | push32 imm32 | 0x14 | shr |
| 0x05 | ld8 | 0x15 | eq |
| 0x06 | ld32 | 0x16 | ne |
| 0x07 | pop | 0x17 | lt |
| 0x08 | st8 | 0x18 | gt |
| 0x09 | st32 | 0x19 | le |
| 0x0A | add | 0x1A | ge |
| 0x0B | sub | 0x1B | dup |
| 0x0C | mul | 0x1C | swap |
| 0x0D | div | 0x1D | call imm32 |
| 0x0E | mod | 0x1E | ret |
| 0x0F | and | 0x20 | printstr |
3. 三层处理函数
第一层:3字节转4字节编码
- 算法:24位混淆 + Base64映射
- 混淆算法:
v = (mem[src_idx] << 16) | (mem[src_idx + 1] << 8) | mem[src_idx + 2] v = rol24(v, 5) ^ 0x55757d v = rol24(v, 11) ^ 0x55757d v = rol24(v, 20) ^ 0x55757d - Base64表:标准Base64字母表
第二层:异或处理
- 简单异或:
dst[i] = src[i] ^ 0x63
第三层:比较函数
- 比较目标:56字节固定数据
4. 逆算法实现
def ror24(x, n):
return ((x >> n) | (x << (24 - n))) & MASK
# 逆变换过程
x ^= 0x55757d
x = ror24(x, 20)
x ^= 0x55757d
x = ror24(x, 11)
x ^= 0x55757d
x = ror24(x, 5)
5. 最终结果
- Flag:
NCTF{1578be15-ad09-4859-9193-5d52585eb485}
题目五:鸡爪流高手
1. 程序概述
- 类型:二进制协议 + 五子棋对战 + SQLite积分系统
- 目标:将玩家分数提升至排行榜第一
- 漏洞:负分处理时的32位零扩展漏洞
2. 协议格式
"GAME" + be32(total_len) + u8(cmd) + payload
- 最大负载:0x400字节
- 命令列表:
- 1: 取Flag
- 2: 查看分数/排名
- 3: 排行榜概览
- 4: 重置环境
- 5: 开始匹配
- 6: 查看棋盘
- 7: 落子
- 8: 认输/取消
3. 积分系统漏洞
积分计算公式
def score_calculate_delta(self, opp, actual):
expected = 1.0 / (1.0 + pow(10.0, (opp - self) / 50.0))
return lround(20.0 * (actual - expected))
漏洞点分析
-
buggy版本不检查负分:
if (old_score <= 9 && delta < 0) { return old_score; // 仅对低分保护 } return old_score + delta; // 可能返回负数 -
32位零扩展问题:
mov edx, eax ; 32位负数零扩展 call sqlite3_bind_int64- 负数
-1(0xFFFFFFFF) → 4294967295
- 负数
4. 利用链构建
初始状态
- 玩家分数:50
- 对手分数池:
[50, 40, 30, 20, 20, 10, 2000000]
步骤分解
-
击败10分bot:
- 使用必胜开局:
5,35,45,55,65,7 - 结果:玩家50→53,对手10→7
- 使用必胜开局:
-
连续输给指定对手:
- 53 → 37(输给20分)
- 37 → 23(输给20分)
- 23 → 15(输给30分)
- 15 → 10(输给38分)
- 10 → -1(输给7分)
-
数据库存储:
- 负数-1零扩展为4294967295
- 成为排行榜第一
5. 对手选择策略
- 使用cmd=5随机匹配
- 非目标对手时,用cmd=8空棋盘取消(不掉分)
- 重复直到匹配到目标分数对手
6. 完整利用流程
cmd=4重置环境- 匹配10分bot并获胜
- 按顺序输给指定分数对手
- 分数下溢后执行
cmd=1获取flag
总结
技术要点归纳
- 多层保护突破:Godot资源加密、RC4伪DLL、运行时热补丁
- 算法逆向:自定义加密算法、魔改Base64、虚拟机指令集
- 约束求解:Z3求解器在哈希碰撞场景的应用
- 整数漏洞利用:负分处理与32位零扩展的组合利用
- 协议分析:自定义二进制协议的逆向与利用
防御建议
- 避免在关键校验中使用弱哈希
- 对用户输入分数进行边界检查
- 使用正确的有符号扩展处理
- 避免在客户端存储核心校验逻辑
- 对资源加密使用强密钥管理
学习价值
这些题目涵盖了从基础逆向到复杂系统漏洞利用的完整技能栈,包括:
- 移动应用逆向(Android JNI)
- 游戏保护机制分析
- WebAssembly逆向
- 虚拟机设计与实现
- 二进制协议安全
- 整数溢出漏洞利用
通过系统学习这些题目,可以建立完整的逆向工程和漏洞利用知识体系。
相似文章
相似文章