PolyVM Self-JIT CrackMe 逆向、调试与静态交付复盘报告
文档编号: CB-POLYVM-20260817-R2
文档状态: Final / Evidence-backed
报告日期: 2026-08-17(Asia/Hong_Kong)
分析类型: PE32/x86、自编译 JIT、轻度反调试、自文件完整性、静态密码交付
当前交付目录: C:\Users\LKROG\Downloads\VMP\Crackme
证据工作区: C:\Users\LKROG\Documents\ChatGPT\VMP\case_crackme_20260817
最终完成判据: 独立运行交付文件,输入固定密码后精确显示 Correct!
0. 阅读指南与结论边界
本报告是对本次 CrackMe 工作的技术复盘、证据索引和交付审计,不是把零散日志重新堆一遍。报告按以下原则组织:
- 原始文件、IDA 数据库、调试器进程、Frida 插桩、离线模型和最终交付是六个不同证据面。
- 静态密码交付完成不等于恢复了原始隐藏密码。
- 绕过完整性判定的消费者不等于重新实现了原始完整性摘要算法。
- 两次 JIT 指纹不同证明本样本在两次已记录运行中发生变异,不等于完成全部随机种子下的语义等价证明。
- 所有地址默认属于本次 SHA-256 绑定的 PE32/x86 样本;新版本必须重新定位,不能照搬。
证据等级统一使用:
| 等级 |
含义 |
本报告写法 |
| Confirmed |
文件字节、MCP 返回、运行输出或哈希直接支持 |
给出具体文件、地址、值或测试 |
| Inferred |
多项观察一致,但仍存在其他实现可能 |
写明推断依据与缺口 |
| Unknown |
没有足够证据、工具未记录或未完成覆盖 |
明确保留未知,不补造结论 |
1. 管理层摘要
1.1 最终结果
本次任务完成了用户要求的静态交付:
- 当前交付文件:
C:\Users\LKROG\Downloads\VMP\Crackme\crackme_correct.exe
- 静态密码:
ColdBrew,长度 8,区分大小写
- 文件大小:252,928 字节
- SHA-256:
D6CE35B4661CE2FA33D019EF7A2B5F80D5D4F5BE72A265FA50332335EE610921
- 运行时不依赖 IDA、x32dbg、Frida、MCP、插件或额外 DLL
- 未新增 PE 节,未增加 overlay,未改变入口 RVA 和文件总大小
- 原始 seed/JIT 构建主流程继续执行;两次独立运行记录到不同 JIT 长度和 FNV-1a 指纹
当前目录直接复验:
crackme_correct.exe --password ColdBrew
Correct!
退出码为 0。错误输入、大小写变化、空输入、短输入和长输入均返回 Wrong password!,退出码为 1。
1.2 核心技术判断
该样本更准确的描述是:
一个原生 PE32/x86 自写 PolyVM/self-JIT CrackMe,包含运行期随机种子、JIT 物化、轻度反调试、自文件完整性和差分输入 oracle。
磁盘上的 .pcode 运行期被物化为机器码,但这不足以把样本归类为某一商业壳或特定版本的 VMProtect。最终交付也没有新增外部壳;它是在原 PE 中实施五个逻辑补丁点。
1.3 完成与未完成
| 交付行 |
状态 |
结论 |
| 目标识别与 PE 基线 |
PASS |
原件哈希、架构、入口、节表、overlay 已绑定 |
| 反调试路径定位 |
PASS |
静态函数与 x32dbg 运行时回读均有证据 |
| 临时动态反调试处理 |
PASS |
失败写入、替代写入、最终回读均有记录 |
| 完整性与四路判定理解 |
PASS(消费者级) |
验证链和最终条件已映射 |
| JIT/IR 运行时捕获 |
PASS |
JIT、加密 IR、解密 IR 已导出并哈希 |
| 单轨迹 VM 解码/重放 |
PASS(样本轨迹级) |
497 动态步骤,test 返回 0 |
| 原始隐藏密码恢复 |
Unknown / 未恢复 |
求解器与已定义枚举空间未得到原始密码 |
| 完整跨种子去虚拟化 |
未完成 |
未建立所有种子/输入下的完整等价证明 |
| 固定静态密码交付 |
PASS |
ColdBrew 成功,负向输入失败 |
| 每次运行变异保留 |
PASS(两次运行证据) |
JIT 大小与指纹均不同 |
| 无插件独立运行 |
PASS |
当前部署 EXE 直接运行通过 |
2. 范围、授权对象与当前目录状态
2.1 分析对象与当前副本
历史分析源路径为:
C:\Users\LKROG\Downloads\VMP\crackme.exe
当前精简交付目录中的原件副本为:
C:\Users\LKROG\Downloads\VMP\Crackme\CrackMe.exe
两者绑定的原件 SHA-256:
C964571FEFE227CF7F44803065B4475EEB410410EFE2C0E7024E2BD06E423F05
2.2 当前交付目录快照
报告重写前,目标目录已被外部精简为 4 个文件:
| 文件 |
大小 |
SHA-256 |
作用 |
CrackMe.exe |
252,928 |
C964571F...423F05 |
原始样本副本 |
crackme_correct.exe |
252,928 |
D6CE35B4...610921 |
最终静态交付 |
crackme_full_debug_report.md |
24,852 |
9487C33C...7B2791 |
本报告旧版,已备份后重写 |
成功.png |
143,039 |
81DB86CC...ECC9CC |
成功运行截图 |
目标目录不再部署验证器脚本或大批调试日志。完整研究证据仍保存在证据工作区和回滚区,本报告按绝对路径引用。
2.3 明确排除项
本次没有声称:
- 恢复原始隐藏密码;
- 完成商业 VMProtect 的通用脱壳;
- 完成所有 seed、所有输入的全量 VM 去虚拟化;
- 重新实现原始文件完整性摘要;
- 证明未来重新编译版本仍可使用相同 RVA/字节补丁;
- 证明 Frida 控制进程记录的
4294967295 是目标 EXE 的真实业务退出码。
3. 证据保全与链路
3.1 基线、备份和工作副本
| 类型 |
路径 |
SHA-256 / 状态 |
| 原始样本 |
C:\Users\LKROG\Downloads\VMP\crackme.exe |
C964571F...423F05 |
| 当前原件副本 |
C:\Users\LKROG\Downloads\VMP\Crackme\CrackMe.exe |
C964571F...423F05 |
| 分析备份 |
C:\Users\LKROG\Documents\ChatGPT\VMP\case_crackme_20260817\backup\crackme.exe |
C964571F...423F05 |
| 调试工作副本 |
C:\Users\LKROG\Documents\ChatGPT\VMP\case_crackme_20260817\work\crackme.exe |
原件工作副本 |
| 工作区交付 |
C:\Users\LKROG\Documents\ChatGPT\VMP\case_crackme_20260817\delivery\crackme_correct.exe |
D6CE35B4...610921 |
| 当前部署交付 |
C:\Users\LKROG\Downloads\VMP\Crackme\crackme_correct.exe |
D6CE35B4...610921 |
原始样本三次基线负向运行均输出 Wrong password!,退出码均为 1;每次运行记录的文件 SHA-256 均未变化。
3.2 证据面分离
| 证据面 |
主要证据 |
能证明 |
不能单独证明 |
| 文件/静态 |
analysis\triage.json、当前 PE 字节 |
节表、导入、字符串、RVA、原始字节 |
实际执行路径 |
| IDB/反编译 |
analysis\crackme.i64、logs\ida_decompile_*.json |
函数、调用关系、伪代码结构 |
运行期 JIT 值和正常环境行为 |
| 调试器 |
logs\x32_*.json、内存 dump |
断点命中、寄存器、内存回读、真实 JIT 入口 |
无调试器下的直接运行结果 |
| Frida |
logs\frida_*.log、logs\finalpoly*.frida.log |
hook 命中、函数因果、两次 JIT 指纹 |
静态文件正确性和原始密码 |
| 离线模型 |
analysis\vm_trace_test.json、solver/GPU JSON |
绑定模型内的重放和搜索结果 |
原目标接受,除非再与目标对比 |
| 交付 |
当前 EXE、补丁清单、直接运行 |
固定密码行为、文件结构、最终哈希 |
全部 VM 语义恢复 |
3.3 IDA 输入身份绑定
logs\ida_metadata.json 记录:
{
"input_file": "C:\\Users\\LKROG\\Downloads\\VMP\\crackme.exe",
"arch": "x86",
"bits": 32,
"endian": "little",
"hash": "c964571fefe227cf7f44803065b4475eeb410410efe2c0e7024e2bd06e423f05"
}
这使 IDB 结论与本报告原件哈希形成直接绑定,而不是仅凭文件名关联。
4. 工具链与环境
| 工具面 |
本次使用方式 |
已记录状态 |
备注 |
| IDA Pro |
IDA 9.4 + MCP,x86 数据库 |
输入元数据匹配;分析后 save=true 关闭 |
IDB 在证据工作区 |
| x32dbg |
x86 调试器 + MCP 127.0.0.1:3000 |
命中验证器与 JIT 入口 |
实际程序:x32dbg.exe |
| Frida |
本机 Frida MCP attach |
script_loaded=true,收到 hook/JIT 事件 |
精确 Frida 版本未在证据 JSON 中固化,记为 Unknown |
| Python |
PE 分诊、JIT/IR 分析、模拟、补丁与回归 |
脚本保存在证据工作区 |
不作为最终运行依赖 |
| Z3/cvc5 |
绑定末端约束的符号求解 |
unsat 或 unknown,按边界报告 |
未产出原始密码 |
| OpenCL |
RTX 4070 Laptop GPU 搜索 |
自测成功;多个有限空间未命中 |
只证明所枚举空间 |
工具联动遵循:
静态建立假设
↓
IDA 恢复函数边界和控制链
↓
x32dbg 证明运行期地址、断点、JIT/IR
↓
离线模拟与求解验证样本语义
↓
Frida 验证因果关系和两次变异
↓
最小静态补丁 + 直接进程回归
5. PE 基线与保护类型判定
5.1 PE 概要
| 字段 |
原件 |
交付版 |
| Machine |
0x14C / i386 |
0x14C / i386 |
| 格式 |
PE32 / x86 |
PE32 / x86 |
| ImageBase |
0x400000 |
0x400000 |
| Entry RVA |
0xEAE7 |
0xEAE7 |
| 节数量 |
7 |
7 |
| SizeOfImage |
274,432 |
274,432 |
| 文件大小 |
252,928 |
252,928 |
| Overlay |
0 |
0 |
5.2 节表
| 节 |
RVA |
VirtualSize |
RawOffset |
RawSize |
观察 |
.text |
0x1000 |
172,541 |
0x400 |
172,544 |
主代码,RX |
.rdata |
0x2C000 |
58,332 |
0x2A600 |
58,368 |
只读数据 |
.data |
0x3B000 |
7,256 |
0x38A00 |
4,096 |
可写数据 |
.pvmig |
0x3D000 |
32 |
0x39A00 |
512 |
元数据及可利用零填充尾部 |
.pcode |
0x3E000 |
8,192 |
0x39C00 |
8,192 |
运行期 JIT 物化区域 |
.rsrc |
0x40000 |
480 |
0x3BC00 |
512 |
资源 |
.reloc |
0x41000 |
7,440 |
0x3BE00 |
7,680 |
基址重定位 |
5.3 保护类型判断
Confirmed:
.pcode 在磁盘上是零/占位区域;
- 程序导入并使用
VirtualProtect、FlushInstructionCache;
- 调试器命中
.pcode 映射地址中的真实执行入口;
- 不同运行中 JIT 长度和指纹不同。
结论:这是自写 native self-JIT/PolyVM 行为。
不做的推断:仅凭节名 .pcode、.pvmig 或运行期物化,不把它认定为特定商业 VMProtect 版本。
6. 静态控制链恢复
6.1 关键函数图谱
| VA |
RVA |
IDA 名称 |
作用 |
证据面 |
0x4016E0 |
0x16E0 |
sub_4016E0 |
主流程、输入、seed、构建、验证、输出 |
IDA |
0x401280 |
0x1280 |
sub_401280 |
初始化/解密完整性元数据 |
IDA |
0x401600 |
0x1600 |
sub_401600 |
XOR 0xA7 解码消息 |
IDA |
0x402EA0 |
0x2EA0 |
sub_402EA0 |
构造 VM/IR 并物化 JIT |
IDA + debugger |
0x408BD0 |
0x8BD0 |
sub_408BD0 |
JIT/IR/hash/canary 检查与 oracle |
IDA |
0x409090 |
0x9090 |
sub_409090 |
完整性、反调试、四路输入、最终布尔 |
IDA + debugger |
0x409660 |
0x9660 |
sub_409660 |
输入复制/变体构造辅助 |
IDA |
0x4098A0 |
0x98A0 |
sub_4098A0 |
运行期 seed 混合 |
IDA |
0x40A5B0 |
0xA5B0 |
sub_40A5B0 |
获取自身路径并读取当前 EXE |
IDA |
0x40A930 |
0xA930 |
sub_40A930 |
解析 PE 节与原始数据范围 |
IDA |
0x40DB10 |
0xDB10 |
sub_40DB10 |
反调试聚合 |
IDA + debugger |
6.2 主流程语义
flowchart TD
A["解析 --password 或控制台输入"] --> B["初始化完整性元数据"]
B --> C["AntiDebug 聚合"]
C --> D["混合 QPC/FILETIME/Tick/PID/RDTSC"]
D --> E["Build VM + encrypted IR + JIT"]
E --> F["Verify: self-file + anti-debug + 4 oracles"]
F --> G["XOR 0xA7 解码结果消息"]
G --> H["打印 Correct!/Wrong password!"]
H --> I["擦除运行期代码/敏感缓冲"]
I --> J["返回 0/1"]
伪代码摘要:
input = parse_password_or_console();
init_integrity_metadata();
anti = anti_debug_aggregate();
seed = anti ^ runtime_seed();
vm = build_vm_and_jit(seed);
ok = verify_self_and_input(vm, input);
print(decode_message_xor_a7(ok));
wipe_jit_and_sensitive_buffers(vm);
return ok ? 0 : 1;
主流程在 VM 构建前调用反调试,最终验证器内部再次调用,因此只处理一次检测不能稳定覆盖完整链路。
7. 反调试机制与 seed 耦合
7.1 检测面
sub_40DB10 聚合的已恢复检查包括:
| 位 |
检查源 |
作用 |
0x01 |
IsDebuggerPresent() |
本地调试器标记 |
0x02 |
CheckRemoteDebuggerPresent() |
远程调试状态 |
0x04 |
NtQueryInformationProcess(ProcessDebugPort) |
DebugPort |
0x08 |
另一类 NtQueryInformationProcess 状态 |
NT 进程调试信息 |
0x10 |
PEB BeingDebugged / NtGlobalFlag & 0x70 |
PEB 启发式 |
0x20 |
GetThreadContext 检查 DR0–DR7 |
硬件断点/调试寄存器 |
7.2 关键设计点
反调试结果不是单纯的最终拒绝布尔值。非零检测结果还会与时间源和常量混合,并参与 VM seed:
vm_seed = anti_debug_result XOR runtime_seed
因此调试器可能同时改变:
- 是否最终接受;
- 当次 VM/IR/JIT 形态;
- 运行时 JIT 长度、布局和指纹。
这也是本次先用临时返回 0 稳定观测,再单独证明最终补丁行为的原因。
7.3 x32dbg 临时处理
本次工作副本装载:
module base = 0x00F30000
entry = 0x00F3EAE7
anti-debug = base + 0xDB10 = 0x00F3DB10
原始 16 字节回读:
55 8B EC 83 EC 10 A1 80 B0 F6 00 33 C5 89 45 FC
第一次普通内存写入返回:
Memory not writable at address: 0x00F3DB10
这个失败是有效证据:代码页写保护生效,普通写入并未成功。随后改用调试器汇编写入:
xor eax, eax
ret
最终回读:
31 C0 C3 83 EC 10 A1 80
只有在前三字节精确为 31 C0 C3 后才继续运行。该修改只存在于工作副本的调试进程中,不是最终磁盘交付证据。
8. 随机种子、VM 构建与 JIT 物化
8.1 seed 源
sub_4098A0 混合:
QueryPerformanceCounter 高低位;
GetSystemTimeAsFileTime 高低位;
GetTickCount64;
- 当前 PID;
RDTSC;
- 两轮 64 位混合运算;
- 外层传入的反调试结果。
8.2 JIT 物化流程
sub_402EA0 的主要阶段:
- 根据 seed 生成或变换 VM IR;
- 生成本次运行对应的 x86 JIT;
VirtualProtect(.pcode, 0x2000, PAGE_READWRITE);
- 把机器码写入
.pcode;
- 填充剩余区域;
FlushInstructionCache;
- 将区域改为
PAGE_EXECUTE_READ;
- 在 VM 上下文保存 JIT/IR 指针、长度、hash 和 canary;
- oracle 执行前后复核状态。
8.3 运行时入口证明
x32dbg 从 VM 上下文读取:
JIT base = 0x00F6E000
JIT size = 0x677 = 1655 bytes
硬件执行断点在 0x00F6E000 实际命中:
EIP = EAX = 0x00F6E000
ESI = 0x0019F82C ; VM context
因此 0x00F6E000 不是静态推测或未执行缓冲区,而是该次运行真实进入的 JIT 入口。
9. 自文件完整性与四路差分 oracle
9.1 自文件读取
最终验证路径会:
- 通过
GetModuleFileNameW 获取当前 EXE 路径;
- 读取磁盘上的当前文件;
- 解析 PE;
- 定位
.text、.rdata 等受保护范围;
- 计算并比较派生完整性状态;
- 把完整性错误并入最终判定。
9.2 输入变体
恢复的四路输入为:
| 路 |
输入 |
| 0 |
原始候选输入 |
| 1 |
首字节 XOR 0xA5 |
| 2 |
尾部追加 0xA5 |
| 3 |
交换前两个字节;短输入时追加 < |
最终成功条件可表达为:
integrity_error == 0
anti_debug_result == 0
oracle(original) == 1
oracle(mutant_1) == 0
oracle(mutant_2) == 0
oracle(mutant_3) == 0
9.3 oracle 前后保护
sub_408BD0 在 JIT 调用前后还检查:
- VM entry 非空;
- VM 上下文和偏移合法;
- JIT/IR 指针与长度合法;
- JIT/IR FNV-1a 或派生值一致;
- canary 未被破坏;
- IR 调用前解密、调用后重新加密;
- 运行期派生 64 位状态一致。
工程含义:只修改提示字符串、只 NOP 一处条件跳转或只篡改一次 oracle 返回值,仍可能被其他检查否决。
9.4 最终交付如何“解决完整性校验”
最终交付采用的是消费者级控制流旁路:
- 原完整性代码和数据仍在文件中;
- 入口
RVA 0x9090 被重定向到固定密码桩;
- 固定密码桩返回与原验证器相同约定的布尔值;
- 原磁盘哈希与四路 oracle 在该交付路径上不再执行。
因此准确表述是:
交付版绕过了原完整性判定的最终消费者,满足固定密码独立运行目标;没有重新计算或伪造原摘要。
10. x32dbg 动态证据
10.1 验证器断点
运行时验证器地址:
base + 0x9090 = 0x00F39090
MCP 返回:
EIP = 0x00F39090
state = paused
stop_reason = breakpoint_or_exception
success = true
快照中的关键寄存器:
ECX = 0x0019F82C ; VM context
EDX = 0x0019F964 ; input object/data
ESP = 0x0019F7E4
EIP = 0x00F39090
EDX 指向内容以 74 65 73 74 开头,即测试输入 test,长度字段为 4。
10.2 导出物
| 文件 |
大小 |
SHA-256 |
analysis\jit_run1.bin |
1,655 |
1585736A757D70706A4F0C3229F1C6B2DE4CF7F23B96175E0670DAC60D225DD7 |
analysis\vm_ir_run1.bin |
1,874 |
818D8B41A479957E4C6571B00488492D9A29CB68AC0AC1F6447A03BF3940FCE5 |
analysis\vm_ir_run1_decrypted.bin |
1,874 |
3D614AF88C04C85A8F5E995D41D0A8445560A79E6748BDDF8226E6BFA75C1136 |
JIT 样本反汇编得到 402 条 x86 指令。该 dump 是运行时内存证据,不是一个独立 PE,也不应称为“脱壳后的可执行文件”。
11. IR 解码、模拟与求解边界
11.1 单次样本恢复
解密 IR:
- 长度:1,874 字节;
- 静态解析:515 条 VM 指令;
- 当前样本涉及 20 个编码 opcode;
- 已映射类别包括 load、算术逻辑、移位/rotate、比较、条件/无条件分支、调用、返回和状态操作。
离线模拟器对 test 执行:
dynamic steps = 497
final pc = 0x750 / 1872
RETURN value = 0
结果与目标拒绝 test 一致。
11.2 末端约束
恢复的四个 32 位比较目标:
0x8A720C26
0xF2F2A8CA
0x9807F752
0x9E7F1DAD
Z3 长度 11 模型返回 unknown;这不是 sat,也不是 unsat,更不是密码。
11.3 cvc5 和 GPU 搜索
| 空间 |
测试量 |
结果 |
| cvc5 长度 1–5 |
绑定符号模型 |
均 unsat |
| 全字母数字长度 6 |
56,800,235,584 |
未命中 |
| 全字母数字长度 7 |
3,521,614,606,208 |
未命中 |
| 全小写长度 8 |
208,827,064,576 |
未命中 |
| 全大写长度 8 |
208,827,064,576 |
未命中 |
| 首字母大写、其余小写长度 8 |
208,827,064,576 |
未命中 |
| 小写+数字长度 8 |
2,821,109,907,456 |
未命中 |
GPU 自测使用 RTX 4070 Laptop GPU,在 456,976 个长度 4 小写输入中成功重新找到 test。这证明搜索实现能命中已知向量,不证明它能恢复未知原始密码。
11.4 专业结论
Confirmed:上述明确定义的空间未命中;长度 1–5 的绑定 cvc5 模型为 unsat;Z3 长度 11 为 unknown。
Unknown:原始密码长度、字符集以及跨 seed 的完整约束模型。
因此停止无界扩大搜索是正确的工程决策;用户完成条件是固定静态密码,不要求证明性恢复原始秘密。
12. Frida 因果验证与多态证据
12.1 配置故障与恢复
Frida MCP 最初因空 device_id 进入错误设备路径。处理过程:
- 备份原配置;
- 临时设置
device_id=local;
- 通过真实 MCP stdio 调用本机 attach;
- 完成 hook 与采样;
- 恢复原配置;
- 用哈希验证恢复结果。
任务前/备份/恢复后:A1BCEE8889B61D84E45A584BEEC3487DF32211F0B7A5B79EC3F2445731A25AA9
临时 local 配置: 297056A953D6CC909F7CAC5075348F9576A7CA248D4B46E14893975AA8B351E2
Restored: true
12.2 因果验证
PID 2332 的 attach:
status = success
script_loaded = true
hook 事件:
anti_debug_bypass returnValue = 0
verifier_bypass input length = 4
stdout = Correct!
这证明主流程在反调试返回 0、验证器返回真时,会沿原消息解码/打印链显示成功。它是运行时因果证据,不代替静态交付测试。
12.3 两次最终交付采样
| 运行 |
PID |
模块基址 |
VM context |
JIT 地址 |
JIT 大小 |
FNV-1a |
输出 |
| 1 |
49,568 |
0x00B60000 |
0x010FFB04 |
0x00B9E000 |
1,095 |
744A5585 |
Correct! |
| 2 |
64,904 |
0x00B60000 |
0x0075FB98 |
0x00B9E000 |
1,372 |
7500421C |
Correct! |
两次运行满足:
same delivery hash
same accepted input
same success output
different JIT size
different JIT fingerprint
Confirmed:两次已记录运行发生变异,且行为保持成功。
Inferred:随机 seed 驱动的 JIT 形态变异机制仍在交付版主流程中工作。
未扩大声称:未对全部 seed 做语义归一化或等价性证明。
Frida 辅助启动记录中的 4294967295 来自控制/暂停包装路径,不作为交付 EXE 的真实退出码。真实退出码以后续直接 subprocess/进程运行结果为准。
13. 静态补丁工程
13.1 设计约束
最终方案必须同时满足:
- 固定密码,而非所有输入无条件通过;
- 保留成功/失败输出和退出码语义;
- 不新增节、不增长文件、不增加 overlay;
- 运行时不依赖分析工具;
- 不使用固定绝对 VA;
- 保留主流程中的 seed 和 JIT 构建;
- 原件不覆盖,交付写入新文件。
13.2 方案选择
最终将验证器入口重定向到 .pvmig 既有 raw slack:
source RVA = 0x9090
target RVA = 0x3D020
rel32 = target - (source + 5)
= 0x3D020 - 0x9095
= 0x33F8B
对应字节:
E9 8B 3F 03 00
第 6 字节以 90 填充,从而完整覆盖原始 6 字节入口序列。
该跳转使用 RVA 间的 rel32,不嵌入绝对 VA。只要两个 RVA 在同一映像内保持关系,装载基址变化不会改变相对位移。
13.3 逻辑补丁清单
| 功能 |
RVA |
文件偏移 |
原值 |
新值 |
| 验证器跳转 |
0x9090 |
0x8490 |
55 8B EC 6A FF 68 |
E9 8B 3F 03 00 90 |
| 固定密码桩 |
0x3D020 |
0x39A20 |
33 字节零填充 |
33 字节验证代码 |
.pvmig VirtualSize |
— |
0x280 |
20 00 00 00 |
00 02 00 00 |
.pvmig Characteristics |
— |
0x29C |
40 00 00 40 |
40 00 00 60 |
| 反调试固定返回 0 |
0xDB10 |
0xCF10 |
55 8B EC |
31 C0 C3 |
逻辑补丁为 5 处;逐字节比较时共有 41 个实际不同字节,分布在 7 个连续差异区间。差异区间数量多于逻辑补丁数,是因为部分替换字节与原零字节相同。
13.4 静态密码桩
完整 33 字节:
83 7C 24 04 08 75 17
81 3A 43 6F 6C 64 75 0F
81 7A 04 42 72 65 77 75 06
B8 01 00 00 00 C3
31 C0 C3
等价语义:
cmp dword ptr [esp+4], 8
jne fail
cmp dword ptr [edx], 0x646C6F43 ; "Cold"
jne fail
cmp dword ptr [edx+4], 0x77657242 ; "Brew"
jne fail
mov eax, 1
ret
fail:
xor eax, eax
ret
它保留了原验证器的布尔约定:成功返回 1,失败返回 0。
13.5 .pvmig 处理
原 .pvmig 的 RawSize 为 512 字节,但 VirtualSize 仅 32 字节。密码桩写入 raw slack 后:
- VirtualSize 扩展为
0x200,使已存在 raw 区间全部被映射;
- Characteristics 从
0x40000040 调整为 0x60000040,增加执行权限;
- 不新增节;
- 不改变 RawSize;
- 不改变文件大小;
- 不产生 overlay。
13.6 被否决的方案
| 方案 |
否决原因 |
只修改 Correct! 字符串 |
只改变显示,不改变布尔与退出码 |
| 所有输入都返回真 |
不满足“静态密码”要求,负向回归无法成立 |
| 只绕过一次反调试 |
最终验证器内还有第二次检测 |
| 覆盖某个单一分支 |
完整性、四路 oracle、hash/canary 仍可能否决 |
| 绝对 VA 跳转/调用 |
与 ASLR 冲突 |
| 与 relocation 覆盖冲突的中间洞 |
跨基址不可靠,已废弃 |
| 持续扩大无界暴力空间 |
无明确完成上界,不是用户必要条件 |
14. 当前交付验证
14.1 文件身份
| 文件 |
当前路径 |
大小 |
SHA-256 |
| 原件副本 |
C:\Users\LKROG\Downloads\VMP\Crackme\CrackMe.exe |
252,928 |
C964571FEFE227CF7F44803065B4475EEB410410EFE2C0E7024E2BD06E423F05 |
| 最终交付 |
C:\Users\LKROG\Downloads\VMP\Crackme\crackme_correct.exe |
252,928 |
D6CE35B4661CE2FA33D019EF7A2B5F80D5D4F5BE72A265FA50332335EE610921 |
14.2 当前直接运行矩阵
所有输入都由当前目录中的 crackme_correct.exe 直接执行,不经过调试器或 Frida:
| 输入 |
长度 |
退出码 |
标准输出 |
结果 |
ColdBrew |
8 |
0 |
Correct! |
PASS |
wrong |
5 |
1 |
Wrong password! |
PASS |
coldbrew |
8 |
1 |
Wrong password! |
PASS |
test |
4 |
1 |
Wrong password! |
PASS |
| 空字符串 |
0 |
1 |
Wrong password! |
PASS |
ColdBrewX |
9 |
1 |
Wrong password! |
PASS |
ColdBre |
7 |
1 |
Wrong password! |
PASS |
这组测试证明:
- 不是无条件成功;
- 密码比较区分大小写;
- 长度必须精确为 8;
- 短、长、空输入不会崩溃;
- 输出和退出码保持双向语义。
14.3 当前原件负向回归
当前 CrackMe.exe --password test:
exit code = 1
stdout = Wrong password!
SHA-256 = C964571FEFE227CF7F44803065B4475EEB410410EFE2C0E7024E2BD06E423F05
原件仍保持基线哈希。
14.4 PE 结构回归
| 检查 |
原件 |
交付版 |
结论 |
| 文件大小 |
252,928 |
252,928 |
相同 |
| 节数量 |
7 |
7 |
无新增节 |
| Entry RVA |
0xEAE7 |
0xEAE7 |
未改入口 |
| SizeOfImage |
274,432 |
274,432 |
相同 |
| Overlay |
0 |
0 |
无 overlay |
| 外部分析工具依赖 |
无 |
无 |
独立运行 |
14.5 成功截图
目录中的 成功.png 记录了控制台输入 ColdBrew 后显示 Correct! 的历史成功画面。截图 SHA-256:
81DB86CC95D80DCE91AA896B81105A6813500A45BD8C5EC451071CC279ECC9CC
截图属于视觉辅助证据;最终 PASS 仍以当前直接进程运行的 stdout 和退出码为准。
15. 故障、恢复与决策复盘
15.1 代码页不可写
现象:普通 memory_write 被拒绝。
根因:目标 .text 映射页当前不可写。
处理:改用 x32dbg assembler 写入。
验收:回读为 31 C0 C3 才继续。
经验:工具返回失败必须作为 checkpoint,不能把“发出写请求”当成“写入成功”。
15.2 Frida 设备选择错误
现象:空 device_id 选择错误设备路径。
处理:配置先备份,临时切 local,通过真实 MCP 协议 attach。
验收:script_loaded=true、hook 事件和 stdout 均到达;配置恢复哈希与任务前一致。
经验:MCP 健康、attach 成功、脚本加载、hook 命中、行为改变是五个不同检查点。
15.3 求解器未给出原始密码
现象:Z3 unknown,cvc5 和多个 GPU 空间未命中。
处理:保留结果和搜索边界,停止无界扩大。
经验:unknown 不是失败密码,也不是不存在;有限枚举只排除有限空间。
15.4 中间补丁与重定位冲突
现象:早期代码洞方案与 relocation 使用范围冲突。
处理:废弃中间产物,改用 .pvmig raw slack 和 E9 rel32。
经验:“能启动一次”不够;代码洞必须同时检查 raw/virtual 范围、权限、relocation 和 ASLR。
15.5 报告与交付目录精简
目标目录后来被精简为原件、交付 EXE、报告和截图四个文件。研究日志与脚本没有被重新灌回交付目录;本报告改从证据工作区引用,保持交付目录轻量。
16. 证据索引
证据根:
C:\Users\LKROG\Documents\ChatGPT\VMP\case_crackme_20260817
16.1 静态与 IDA
| 证据 |
相对路径 |
| PE 分诊 |
analysis\triage.json |
| IDA 数据库 |
analysis\crackme.i64 |
| IDA 输入元数据 |
logs\ida_metadata.json |
| 主函数反编译 |
logs\ida_decompile_main.json |
| 反调试反编译 |
logs\ida_decompile_antidebug.json |
| seed 反编译 |
logs\ida_decompile_seed.json |
| VM 构建反编译 |
logs\ida_decompile_vm_build.json |
| 最终验证器反编译 |
logs\ida_decompile_vm_check.json |
| JIT oracle 反编译 |
logs\ida_decompile_jit_oracle.json |
| 完整性路径 |
logs\ida_decompile_integrity_path.json |
| 完整性 hash |
logs\ida_decompile_integrity_hash.json |
| IDA 保存关闭 |
logs\ida_close.json |
16.2 x32dbg
| 证据 |
相对路径 |
| 模块基址与工作副本 |
logs\x32_main_module.json |
| 反调试原字节 |
logs\x32_antidebug_original.json |
| 普通写入失败 |
logs\x32_antidebug_patch.json |
xor eax,eax 写入 |
logs\x32_assemble_xor.json |
ret 写入 |
logs\x32_assemble_ret.json |
| 最终回读 |
logs\x32_antidebug_stub_readback.json |
| 验证器命中 |
logs\x32_run2_verifier.json |
| 验证器寄存器快照 |
logs\x32_verifier_snapshot.json |
| JIT 指针/大小 |
logs\x32_jit_base_eval.json、logs\x32_jit_size_eval.json |
| JIT 入口命中 |
logs\x32_jit_entry_context.json |
| JIT/IR dump |
logs\x32_dump_jit.json、logs\x32_dump_ir.json、logs\x32_dump_ir_decrypted.json |
16.3 VM、求解器和 GPU
| 证据 |
相对路径 |
| JIT 二进制 |
analysis\jit_run1.bin |
| JIT 反汇编 |
analysis\jit_run1.asm、analysis\jit_run1.disasm.json |
| 加密/解密 IR |
analysis\vm_ir_run1.bin、analysis\vm_ir_run1_decrypted.bin |
| 动态 VM trace |
analysis\vm_trace_test.json |
| Z3 结果 |
analysis\z3_solution.json |
| cvc5 结果 |
analysis\cvc5_len1.json 至 analysis\cvc5_len5.json |
| GPU 自测 |
analysis\gpu_selftest2.json |
| GPU 搜索 |
analysis\gpu_*.json、analysis\gpu_*.cl |
16.4 Frida 与交付
| 证据 |
相对路径 |
| 因果 attach |
logs\frida_force_attach.json |
| 因果 hook |
logs\frida_force_hook.log |
| 因果 stdout |
logs\frida_force_stdout.txt |
| 配置恢复 |
logs\frida_config_restore.json |
| 最终运行 1 |
logs\finalpoly1.frida.attach.json、logs\finalpoly1.frida.log、logs\finalpoly1.stdout.txt |
| 最终运行 2 |
logs\finalpoly2.frida.attach.json、logs\finalpoly2.frida.log、logs\finalpoly2.stdout.txt |
| 补丁清单 |
delivery\patch_manifest.json |
| PE 报告 |
delivery\pe_report.json |
| 历史工作区回归 |
delivery\verification.json |
| 历史部署回归 |
delivery\deployed_run.json、delivery\deployment.json |
17. 可复现检查清单
对当前精简交付目录,最小复核不需要任何插件:
$root = 'C:\Users\LKROG\Downloads\VMP\Crackme'
Get-FileHash -Algorithm SHA256 -LiteralPath "$root\CrackMe.exe"
Get-FileHash -Algorithm SHA256 -LiteralPath "$root\crackme_correct.exe"
& "$root\crackme_correct.exe" --password ColdBrew
$LASTEXITCODE
& "$root\crackme_correct.exe" --password wrong
$LASTEXITCODE
期望:
ColdBrew -> Correct! -> exit 0
wrong -> Wrong password! -> exit 1
如需复核运行期 JIT/IR,应从证据工作区复制到新的测试副本进行,不应直接覆盖当前交付文件或原件。
18. 回滚与恢复
18.1 报告重写回滚
本次报告重写前版本保存在:
C:\Users\LKROG\Downloads\VMP\.rollback\crackme-report-rewrite-20260817-031043\crackme_full_debug_report.before.md
旧版 SHA-256:
9487C33C0D6535D0D8578BEEB743473FA852B14AB848EC95BD4F9F630E7B2791
恢复时只需先确认当前报告不再需要,再把备份复制回:
Copy-Item -LiteralPath `
'C:\Users\LKROG\Downloads\VMP\.rollback\crackme-report-rewrite-20260817-031043\crackme_full_debug_report.before.md' `
-Destination 'C:\Users\LKROG\Downloads\VMP\Crackme\crackme_full_debug_report.md'
18.2 二进制回滚
原件没有被覆盖。若不再需要静态交付,删除 crackme_correct.exe 即可回到只保留原始样本的状态。不要把交付文件重命名覆盖 CrackMe.exe 后再声称原件未改。
19. 风险、限制与后续研究方向
19.1 当前限制
- 原始隐藏密码仍为 Unknown。
- VM 解码以一个已捕获 IR/JIT 样本和一条主要负向轨迹为中心,不是跨 seed 全覆盖。
- 两次多态证据验证“指纹不同且输出相同”,未完成 handler 级语义归一化比较。
- 精确 Frida 版本未固化在现有证据 JSON 中。
- 静态补丁绑定当前 SHA-256 和 RVA;重新编译后必须重新分析。
- 完整性采用控制流旁路,不是完整算法恢复。
19.2 如继续研究,优先级建议
| 优先级 |
方向 |
验收标准 |
| P1 |
多 seed IR 规范化 |
至少 10 次运行,归一化 opcode/CFG 后比较语义 |
| P1 |
完整 VM state 模型 |
PC、寄存器、栈、flags、memory、call、exception 均有测试 |
| P2 |
完整性摘要恢复 |
受保护区、摘要算法、期望值派生和消费者全部复现 |
| P2 |
原始密码约束完善 |
模型与未修改原目标进行正负向差分验证 |
| P3 |
跨构建定位 |
使用签名与结构定位替代固定 RVA,并在第二构建验证 |
20. 最终完成审计
| 原始要求 |
状态 |
精确证据 |
| 无插件过反调试 |
PASS |
交付 EXE 直接运行;RVA 0xDB10 为 31 C0 C3 |
| IDA + 调试器 + Frida 联合应用 |
PASS |
输入绑定 IDB、x32dbg 命中、Frida attach/hook/JIT 事件 |
| 解决完整性校验 |
PASS(旁路) |
RVA 0x9090 重定向到固定密码桩,不进入原消费者 |
| 自写多态虚拟化保留 |
PASS(两次运行) |
1,095/744A5585 与 1,372/7500421C |
| 每次运行变异 |
PASS(已采样两次) |
同交付哈希、同输入、同输出、不同 JIT 指纹 |
| 轻度反调试 |
PASS |
检测面已映射,交付入口静态返回 0 |
| 自文件完整性分析 |
PASS(机制级) |
自文件读取、节定位、四路 oracle、最终条件已恢复 |
| 随机种子保留 |
PASS |
seed/JIT 构建仍在主流程,双运行变异可见 |
| 自编译/JIT 保留 |
PASS |
调试器命中 JIT 入口;交付版两次 JIT 指纹可见 |
| 静态密码 |
PASS |
ColdBrew,严格 8 字节、区分大小写 |
| 无新增壳 |
PASS |
7 节不变、overlay 0、文件大小不变、无外部插件 |
显示 Correct! |
PASS |
当前交付直接运行:stdout=Correct!,exit=0 |
最终结论
当前交付文件 C:\Users\LKROG\Downloads\VMP\Crackme\crackme_correct.exe 已满足本次明确完成条件:使用 --password ColdBrew 独立运行时精确显示 Correct! 并以退出码 0 结束;错误、大小写变化、空、短和长输入均被拒绝。
事实边界保持不变:ColdBrew 是本次静态补丁植入的交付密码,不是已恢复的原始 VM 隐藏密码;完整性是消费者级旁路,不是摘要算法重建;完整跨 seed 去虚拟化仍未完成。