
检测 Unicorn 的差异点与消除方法
本文写确定能杀的差异:真机行为由架构固定或
时间源完全受我们掌控,模拟器可 100% 对齐消除。真机自身就因设备/微架构而异的编码(写回寄存器重叠、
LDP 同目标,都是 CONSTRAINED UNPREDICTABLE)不当检测点处理(末尾附背景)。
结构:先给检测方的全部探测片段(app 怎么探 Unicorn),再讲统一的信号注入回调机制,最后逐点消除
(改哪里、加什么回调、代码片段)。
app 一侧常用这几类探测。先注册 SIGILL/SIGSEGV handler + sigsetjmp 兜住会 fault 的编码,再逐条对比结果。
差异分两类,处理方式不同:
信号类检测点不逐条改引擎译码,而是用魔改 unicorn 新增的两个 API 把 SIGILL 投给 guest,复刻真机信号行为。
第一步:增加"信号处理回调"(UC_HOOK_SIGNAL)——整套的核心。 uc_signal_request() 请求信号后,引擎
停下当前 TB、在 uc_emu_start() 返回前回调它,由你决定把信号投给谁:
第二步:注册指令回调抓可疑指令(UC_HOOK_INSN)。 魔改 unicorn 的 UC_HOOK_INSN 支持按指令类型注册
MRS/MSR/SYS/SYSL,回调能拿到系统寄存器编码 op0/op1/crn/crm/op2:
第三步:回调里判真机行为,命中就 uc_signal_request。 uc_err uc_signal_request(uc_engine *uc, int signo)
请求信号——引擎停下、回到 on_signal;命中"真机会 SIGILL"就调它,不命中 return 0 交回引擎。
信号语义、时序、寄存器上下文都跟真机一致,探测方分不出信号由谁处理。改寄存器/PC 必须走 uc_reg_write /
uc_signal_request,回调返回值是"是否已处理"而非结果值。
为什么百分百:guest 能读到的所有时间源——CNTVCT_EL0/CNTPCT_EL0/CNTFRQ_EL0、
clock_gettime/gettimeofday——全部经过我们的回调。让它们返回一个"随已执行指令数单调递增"的虚拟时钟,
两次采样的差值就正比于"做了多少工作"、而不是逐指令翻译的真实墙钟开销,时序检测彻底失效。检测方拿不到任何
不受控的设备时间源。(前提:虚拟时钟必须单调、跨所有源自洽,否则不同源交叉比对会露馅。)
怎么处理——CNTVCT/CNTPCT 走 MRS 回调返回虚拟计数器(on_sysreg 里加一个分支):
clock_gettime/gettimeofday 走外部调用/SVC 回调拦截,用同一个虚拟时钟换算,和 CNTVCT 同源:
引用:Rethinking anti-emulation techniques(<324K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3N6$3&6D9j5h3u0Q4x3X3g2C8M7W2)9J5c8X3c8G2N6$3&6D9L8$3q4V1M7#2)9J5c8Y4q4W2L8i4g2Q4x3X3g2H3k6r3k6Q4x3U0k6Y4N6q4)9K6b7W2)9J5z5g2!0q4y4g2)9^5c8W2)9^5c8q4!0q4y4W2!0m8z5q4!0m8x3g2!0q4y4W2)9^5b7W2)9&6c8W2!0q4y4q4!0n7z5q4)9^5z5g2!0q4y4g2)9^5z5q4)9^5y4W2!0q4y4#2!0n7x3g2!0n7b7W2!0q4y4q4!0n7z5g2)9^5b7R3`.`.
"时序差异(timing)";ARM DDI 0487 的 CNTVCT_EL0/CNTFRQ_EL0 语义。
为什么百分百:EL0 访问 EL1-only 系统寄存器/系统指令是架构强制 UNDEF(访问权限由架构定义,非
UNPREDICTABLE),所有真机一致 SIGILL;未分配编码也一律 UNDEF。原版 QEMU 因 guest 以过高特权运行而照常执行,
未分配 MRS 甚至返回一个内核态地址(信息泄漏)。检测片段见 §一.2。
怎么处理——MRS/MSR 回调(on_sysreg)按编码判 EL0 是否禁访;si_code 由 on_signal 投递时构造:
SYS/SYSL 回调(on_sys)按 op0/op1 判缓存/地址翻译类:
EL0 合法程序不会碰这些编码,SIGILL 不误伤正常执行。
遗留:msr daifset,#2(MSR 立即数写 PSTATE.DAIF,不经系统寄存器回调)。默认 SCTLR_EL1.UMA=0 时 EL0 写
DAIF 真机 UNDEF→SIGILL,同类、也可杀,但走 MSR 立即数编码,要在引擎译码或 CODE 回调里另处理,当前未做。
引用:INSDET(arxiv 2105.14273)"行为差异"类;Rethinking anti-emulation techniques
(<c32K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6H3N6$3&6D9j5h3u0Q4x3X3g2C8M7W2)9J5c8X3c8G2N6$3&6D9L8$3q4V1M7#2)9J5c8Y4q4W2L8i4g2Q4x3X3g2H3k6r3k6Q4x3U0k6Y4N6q4)9K6b7W2)9J5z5g2)9K6b7V1q4d9e0b7`.`. DDI 0487 系统寄存器 EL 访问约束。
真机对某些编码 UNDEF→SIGILL,原版 QEMU 照常执行。两处 load/原子指令,一个在引擎译码修、一个在回调修。
检测片段见 §一.3。
为什么百分百:ldp x9,x9,[x10](同一寄存器被加载两次)架构上是 CONSTRAINED UNPREDICTABLE,但实测
三台真机(Pixel8/ARMv9、Pixel6a/Cortex-X1、Pixel4XL/Cortex-A76)一致 SIGILL——不像"写回重叠"那样 ARMv8/v9
分歧,所以作为可靠检测点收录。这是普通可解码 load、不 trap,只能在引擎译码阶段判定。
怎么处理——translate-a64.c 的 disas_ldst_pair(load pair 译码):
为什么百分百:CASP 要求 Rs、Rt 为偶数寄存器对;Rs<0>==1 || Rt<0>==1 时架构定义为 UNDEFINED(强制,
非 UNPREDICTABLE),所有真机一致 SIGILL。CASP 引擎不支持、走非法指令回调软件模拟,原版模拟时不校验奇偶。
怎么处理——中断回调(UC_HOOK_INTR,intno==1 非法指令分支)模拟 CASP 前先校验奇偶,奇数直接 SIGILL:
引用:INSDET(2105.14273)"QEMU UNDEFINED-check bug"类;ARM DDI 0487(CASP 奇寄存器 UNDEFINED、
LDP 同目标 CONSTRAINED UNPREDICTABLE);LDP 同目标跨设备一致 SIGILL 为本项目多设备实测(见 024)。
为什么百分百:PAC 能力随设备而变(旧机没有、新机有、有的带 FPAC),按 host-gated 做——读宿主真机的
PAC 特性镜像进 VM,VM 行为始终等于"这台真机应有的行为",任意宿主都对齐。检测片段见 §一.4;往返
(pacia;autia 同 modifier / pacia;xpaci)两边都还原,不能用作检测。
PAC 的应对在引擎(QEMU fork)里改,分四处:
(a) host-gated 使能 —— cpu.c 的 arm_cpu_reset(复位阶段,arm_rebuild_hflags 之前):
(b) 去掉容器不该有的陷入 —— pauth_helper.c 的 pauth_check_trap(改按执行档位判):
(c) 加位(值类):使能后引擎 pacia 用自己的密钥真算 PAC 位写回——真机加位、VM 也加位。单签的具体位值
不必跟真机一致:签名密钥每进程随机、EL0 不可读,探测方无法预测正确值、只能查"有没有变化",而 VM 已加位,
这条探测就堵死。(使能后引擎既有行为,无需额外改。)
(d) 坏认证触发信号(FEAT_FPAC) —— pauth_helper.c 的 pauth_auth(认证失败分支):
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于 4小时前
被黑色渡鸦编辑
,原因: