初探虚拟机保护的设计思路与逆向
l1ttl3f41ry
2026年7月
VM 保护效果好,很大程度上就是因为解析起来要投入的精力比较多、流程繁琐。 但 AI 的能力恰恰适合解析这些繁琐的流程。 人类则可以专注于思维和指挥,不用再迷失于重复劳动、烦人的基础操作。
出一个基于 VM 的 CTF 逆向题目
解一道 VM 题(自解or互解)
可以理解为rev1专题考虑bonus后,满分 115 分。
不以代码规模、混淆强度或"能不能不被 AI 一把梭"作为唯一评价标准。 只要 VM 架构自洽、核心校验逻辑确实运行在 VM 中、题目可稳定复现,并且 report 讲清楚设计思路,就能拿到较高的基础分。
有意识的设计:指令集设计、bytecode 编码、dispatcher 隐藏、控制流/数据流复杂化、反侧信道考虑、自动化分析难点、有意识的反 AI 设计……
加分点不是简单地"把别人/AI 难住"——而是能清楚说明:采取了哪些保护措施,这些措施具体增加了哪些分析成本,以及你如何保证题目仍然可解。
我们鼓励有设计意图的 VM 题,而不是不可解、不可复现、或单纯堆工作量的题。
flag 核心校验逻辑如何防止侧信道等绕过 VM 保护的手段?
如何让控制流更加复杂,增加静态分析成本?
如何藏起明显特征,让人更难找到 dispatcher、state 等?
如何让执行流又臭又长,加大 trace 分析的工作量?
(仅供参考,不是必须全都做到——挑一两个方向做深,比全都浅尝更有说服力)
解题很少一帆风顺,踩过的坑、遇到的难关,值得分享。 一路到底、隐去所有探索分支的 writeup 适合比赛提交,但不适合经验交流——尤其是 AI 一键生成的 WP,看多了会审美疲劳。
可以找同学解对方出的题(比较有趣),也可以自己出自己解——但题解同样要从解题者角度写。
不禁止 AI 辅助写 report/writeup,但浓重的 AI 味会酌情扣分,有小巧思会酌情加分。交付前请自己通读、审查一遍——如果连自己都不想读下去,大概不会有高分。
先讲清楚思路,再展开细节——不要一开始就淹没在细节里
不只讲"怎么做",也讲"为什么这么做",读起来会舒服很多
不影响思路的无趣细节可以略过,避免又臭又长
内容丰富度适中:不故意堆砌复杂度,也不能没东西可讲
交你写的题解,可以是解同班别人出的题——请写清楚出题人信息并附上题目附件。
scuffed.online/b01lers-2026 — A Cool Antidecompilation Trick
题解语言不限,找一个出题质量高、难度适合自己的题目去解,比啥都重要。
以 my_vm_example 为例,我们要保护的算法是:
my_vm_example
!?计算机系统I?!
寄存器分配不是硬件强制,而是写汇编程序时的约定。跟真实 ISA 一样,参数传递、调用约定也是靠约定。
以 RISC-V 为例的指令编码格式(R/I/S/B/U/J-type)
load r4, r0 opcode=2 RA=4 RB=0
load r4, r0
→ word = 0x2800
roli r4, 3 opcode=8 RA=4 imm=3
roli r4, 3
→ word = 0x8803
opcode 0 保留为非法指令,halt 放在 15——这样全 0 不会被误解释成合法操作。
注意 or r6, r4 之后没有跳转判断,错误只被累积——这是常量时间比较的关键。
or r6, r4
Fetch → Decode → Dispatch → Execute
从 bytecode 到可执行程序,最常见的写法是 switch-case dispatch(右图)
Fetch → Decode(拆字段)→ Dispatch(switch)→ Execute(改状态)。
$ make clean && make all && make test python3 asm.py check.asm Wrote check.h (23 instructions) Wrote check.lst gcc ... -o vm_check challenge.c Testing correct flag... ./vm_check "flag{w3lc0me_VM}" Correct Testing wrong flag... ./vm_check "flag{w3lc0me_VN}" Wrong
check.asm → asm.py → check.h → 编译进 vm_check → 运行时解释执行
check.asm
asm.py
check.h
vm_check
下一步:Part.4 把这些产物当成黑盒,从外部观察怎么识别、怎么拆解这台 VM。
easyvm 题目的 dispatcher 部分控制流图
push eax push ebx push ecx push edx push esi push edi push ebp ; ... 保存更多寄存器/flags jmp vm_init ; 进入 VM
pop ebp pop edi pop esi pop edx pop ecx pop ebx pop eax ; ... 与 push 顺序相反 ret ; 恢复原程序流
本质是不断提升抽象层次:从机器码 → 汇编指令的意义 → 整段程序的算法。 是不是有种重走 IDA 开发老路的感觉?如今的先进工具,就是帮我们把前面繁琐的事情做好了。
Instruction_init:输入 opcode 和 imm,写入 out 对象对应字段
这个对象类型可以认为是 instruction:每条 instruction 由 opcode + imm 构成
提取出来的原始字节码(op / imm / call 地址)
图一:字节码翻译成伪汇编指令的示例
图二:整段伪汇编理解完之后,程序真正在做的事情(like TEA,但魔改了不少地方)