-
-
[原创]你的特征码死过几次?死在 rel32、jcc 还是 RIP 位移上?
-
发表于: 2026-10-24 09:52 681
-
先说结论:不是你选错了地址,是你把「会变的字节」写进了不会变的模式里。
大多数人写特征码的流程是:在调试器里找到那条 mov rax, [rip+xxx],选中它和后面十几条指令,复制字节,贴进代码。这条模式当天一定好用。下次更新,它死了。
为什么死?因为 x86-64 里有一类字节,它的值等于两个地址之间的距离:
48 8B 05 08 4C A1 00 mov rax, [rip+0xA14C08]
└─ 这 4 个字节是 disp32,等于目标地址减去下一条指令的地址
E8 3A 22 01 00 call 0x...(同样是距离)
0F 84 C1 00 00 00 jz 0x...(同样是距离)
只要这个函数上面多插了一条指令,函数整体下移,目标地址也跟着下移——距离不变。但如果补丁在中间插了代码、或者目标数据段搬了位置,距离就变了。这 4 个字节里的任何一个变了,你的模式就整条失效,哪怕周围 20 个字节一个没动。
所以正确的做法是:距离类的操作数一律打码,操作码和寄存器保持精确。
48 8B 05 ?? ?? ?? ?? 48 85 C0 74 ?? 48 8B 88 ?? ?? ?? ??
↑ 这部分告诉你"这是 mov rax,[rip+x] 后接非空判断"
↑ 这部分本来就该变
这样写出来的模式,抗的是"代码位置变化",而不是碰运气。
第二个坑:唯一性。你自己拼的模式在 100MB 的模块里命中 7 次,代码取第一个,于是有时候对有时候错——这种 bug 能查三天。生成模式必须验证命中数恰好为 1,不是"看起来挺长应该唯一"。
第三个坑:地址正确 ≠ 结构正确。签名找到的是代码,你要的通常是数据。中间隔着几层指针、几个偏移。这些东西得显式写下来,不能猜。
签名命中 → rip 解引用 → 得到对象 → 加 0x10 → 再解引用 → 读 int32
写成这样,下次更新时你能一眼看出是哪一层断了,而不是对着一个错误的值发呆。
我用这三个原则做了个小工具 PatchProof(Python 实现,纯只读,不装驱动),自动完成打码 + 验唯一 + 走链,还带 verify 命令拿新旧两个版本对跑,逐条报 stable / broken / ambiguous,退出码可以直接进 CI。
工具主页:bb9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3j5i4c8U0K9s2m8J5L8$3!0X3i4K6u0W2j5i4m8H3i4K6u0W2N6$3!0J5K9$3u0#2k6r3c8&6i4K6u0W2K9r3!0K6N6l9`.`.
获取与定制:cabK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6S2k6X3c8A6j5h3&6Q4x3X3g2U0L8$3#2Q4x3V1k6S2i4K6u0r3M7r3q4@1j5$3S2H3M7X3!0G2k6R3`.`.
赞赏
赞赏
雪币:
留言: